Недавно наткнулся на занимательный 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)