Недавно наткнулся на занимательный merge request по замене зависимости isNumber. Удивительно было в целом осознавать, что как такого универсального метода по определению числа в переменной нет в базовой концепции JavaScript. И данная проблема породила npm-репозиторий isNumber c почти 72 миллионами еженедельных скачиваний на сентябрь 2024 года. Но стоит ли в очередной раз использовать мизерную зависимость в своём проекте? Предлагаю взглянуть на решение, представленное в ранее сказанном mr-е.
Разбор решения
Концепция isNumber проста: функция, в аргументе которого должна быть определяемая переменная, должна вывести нам true или false; true если да, false если нет. Всё просто!
Вот так выглядит данная функция в реализации:
const isNumber = (v) => (typeof v === "number" && v - v === 0) || (typeof v === "string" && Number.isFinite(+v) && v.trim() !== "");
Выглядит тяжко, не так ли? Давайте разберём каждый участок кода и выясним, решает ли эта функция нашу задачу.
Функция принимает аргумент v
(variable), с которым и предстоит работать.
Суть работы заключается по-большей части в том, чтобы отловить любую выдачу типа number
в данной переменной.
Первая часть проверяет напрямую числовое значение(даже тот же Infinite)
С
typeof v === "number"
всё вполне ясно. Прямая проверка типаv - v === 0
будет возвращать true в случае с числом, так как если бы мы работали, например, со строкой, то такое выражение возвращало быNaN
. Но в случае с массивами это бы не прокатило и данное выражение выдало бы нам тоже 0. Двигаемся дальше.
Вторая часть проверяет строки, которые могут быть преобразованы в числа.
typeof v === "string"
проверяет, является ли переменнаяv
строкой.Number.isFinite(+v)
— здесь строкаv
приводится к числу с помощью унарного оператора+
. Если после преобразования строка является конечным числом (то есть неNaN
, неInfinite
), тоNumber.isFinite
вернётtrue
. Это важно для того, чтобы отсеивать строки, которые не могут быть корректно преобразованы в числа (например,"abc"
или пустые строки).
Унарный, то есть применённый к одному значению,
+
ничего не делает с числовыми значениями. Но если переменная не число, унарный плюс преобразует его в число.
v.trim() !== ""
— эта проверка удаляет пробелы в начале и в конце строки и проверяет, что строка не пуста. Это нужно для того, чтобы отсеивать строки, состоящие только из пробелов, которые могут быть преобразованы в0
, но на практике не являются корректными представлениями чисел.
Общий взгляд
Подытоживая мы видим две части, которые выполняют следующие задачи:
Проверка чисел (
typeof v === "number" && v - v === 0
): Важно не только проверять тип, но и исключатьNaN
, который имеет тип"number"
, но фактически не является числом. Проверка черезv - v === 0
— это эффективный способ исключитьNaN
, так как для всех других чисел разница числа с самим собой всегда равна нулю.Проверка строк (
typeof v === "string" && Number.isFinite(+v) && v.trim() !== ""
): Строки, которые можно преобразовать в числа, часто встречаются в веб-разработке, особенно при работе с формами или API, где данные передаются в виде строк. Важно правильно обработать строки, которые могут быть преобразованы в числа, и отсеять пустые строки или строки, состоящие только из пробелов. ИспользованиеNumber.isFinite
позволяет проверить, является ли преобразованное значение конечным числом.
Так что же там с репозиторием?
Пакет to-regex-range
, речь о котором шла в начале статьи имеет на момент создания merge request-а 43 миллиона скачиваний, а изначальный размер пакета составляет 33Kb. Важно учесть, что основная логика данного пакета состоит из одного среднего по размерам .js файла с парой зависимостей.
После замены пакета isNumber
на собственное решение из зависимостей обнаружили, что to-regex-range
стал легче на 10Kb. Но эта цифра кажется не играющей, но факт остаётся фактом, что по-итогу небольшое изменение привело к сокращению еженедельного трафика скачивания на 440 гигабайт, сократив с 1.5ТB до 1.0TB. Это уже звучит действительно внушительно.
Package size report
===================
Package info for "to-regex-range@5.0.1": 33 kB
Released: 2019-04-07 06:04:37.03 +0000 UTC (277w2d ago)
Downloads last week: 43,837,006
Estimated traffic last week: 1.5 TB
Removed dependencies:
- is-number@7.0.0: 10 kB (30.06%)
Downloads last week: 43,875,245
Downloads last week from "to-regex-range@5.0.1": 43,837,006 (99.91%)
Estimated traffic last week: 440 GB
Estimated traffic from "to-regex-range@5.0.1": 440 GB (99.91%)
Estimated package size: 33 kB → 23 kB (69.94%)
Estimated traffic over a week: 1.5 TB → 1.0 TB (440 GB saved)
Комментарии (41)
Andchir
17.09.2024 20:15+7После замены пакета
isNumber
на собственное решениеНа самом деле там нет собственного решения. Просто взяли код функции из пакета и сделали из него однострочник.
ganqqwerty
17.09.2024 20:15+7Чего-то я не понял. Чей траффик уменьшился? Как инлайн функции связан с траффиком?
Roman2dot0
17.09.2024 20:15+1Связан так, что некоторые выкачивают десятки гигабайт заисимостей ежедневно при сборке, т.к не ставят кэш сервер.
Andchir
17.09.2024 20:15Тут нужно уточнить, что кэширующий сервер нужен только для очень большой команды разработчиков. Для одного разработчика он не нужен (у npm есть свой кэш).
VADemon
17.09.2024 20:15В стародавние времена люди подключали вундер-проги сжатия HTTP-трафика, потому что протокол позволял. Прокси сами, на своих сетях устанавливали провайдеры, кэшируя запросы в пределах своих сетей. Не без причины провайдеры-же организовывали сервисы в интранете, экономя трафик и создавая внутригородской движ. Разумеется, популярностью пользовался Bittorrent внутри ISP.
Это потом интернет стал быстрее и дешевле, государства наглее, а правоторговцы ярее. И лавочка прикрылась, а HTTP покрылся TLS.
Я бы не отказался от кэша Steam (программа есть), дистрибутивов Linux (по HTTP) и других. Да, не всем поможет (у меня последняя миля - VDSL). Банальнейший пример: обновлять три системы на дистрибутиве с rolling release. Большая часть времени уходит на скачивание. Так что может и сам себе прокси поставлю.
VADemon
17.09.2024 20:15Проверка через v - v === 0 — это эффективный способ исключить NaN, так как для всех других чисел разница числа с самим собой всегда равна нулю.
Нет, здесь также бесконечности отсекаются (зачем-то).
Rorg
Где именно в начале статьи идет речь о пакете
to-regex-range
? (Если конечно не переходить по ссылке на оригинальный mr)