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

  1. С typeof v === "number" всё вполне ясно. Прямая проверка типа

  2. v - v === 0 будет возвращать true в случае с числом, так как если бы мы работали, например, со строкой, то такое выражение возвращало бы NaN. Но в случае с массивами это бы не прокатило и данное выражение выдало бы нам тоже 0. Двигаемся дальше.

Вторая часть проверяет строки, которые могут быть преобразованы в числа.

  1. typeof v === "string" проверяет, является ли переменная v строкой.

  2. Number.isFinite(+v) — здесь строка v приводится к числу с помощью унарного оператора +. Если после преобразования строка является конечным числом (то есть не NaN, не Infinite), то Number.isFinite вернёт true. Это важно для того, чтобы отсеивать строки, которые не могут быть корректно преобразованы в числа (например, "abc" или пустые строки).

Унарный, то есть применённый к одному значению, + ничего не делает с числовыми значениями. Но если переменная не число, унарный плюс преобразует его в число.

  1. v.trim() !== "" — эта проверка удаляет пробелы в начале и в конце строки и проверяет, что строка не пуста. Это нужно для того, чтобы отсеивать строки, состоящие только из пробелов, которые могут быть преобразованы в 0, но на практике не являются корректными представлениями чисел.

Общий взгляд

Подытоживая мы видим две части, которые выполняют следующие задачи:

  1. Проверка чисел (typeof v === "number" && v - v === 0): Важно не только проверять тип, но и исключать NaN, который имеет тип "number", но фактически не является числом. Проверка через v - v === 0 — это эффективный способ исключить NaN, так как для всех других чисел разница числа с самим собой всегда равна нулю.

  2. Проверка строк (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)


  1. Rorg
    17.09.2024 20:15
    +8

    Пакет to-regex-range, речь о котором шла в начале статьи…

    Где именно в начале статьи идет речь о пакете to-regex-range ? (Если конечно не переходить по ссылке на оригинальный mr)


  1. Andchir
    17.09.2024 20:15
    +7

    После замены пакета isNumber на собственное решение

    На самом деле там нет собственного решения. Просто взяли код функции из пакета и сделали из него однострочник.


  1. ganqqwerty
    17.09.2024 20:15
    +7

    Чего-то я не понял. Чей траффик уменьшился? Как инлайн функции связан с траффиком?


    1. Andchir
      17.09.2024 20:15
      +6

      Видимо трафик npmjs.com, который создавали разработчики. Но кого волнует трафик npmjs.com?


      1. Andchir
        17.09.2024 20:15
        +3

        Кстати, автор этого PR также рекомендует делать README Вашего пакета как можно меньшего размера, т.к. это тоже экономит трафик :) Такая себе рекомендация.


    1. Roman2dot0
      17.09.2024 20:15
      +1

      Связан так, что некоторые выкачивают десятки гигабайт заисимостей ежедневно при сборке, т.к не ставят кэш сервер.


      1. Andchir
        17.09.2024 20:15

        Тут нужно уточнить, что кэширующий сервер нужен только для очень большой команды разработчиков. Для одного разработчика он не нужен (у npm есть свой кэш).


        1. VADemon
          17.09.2024 20:15

          В стародавние времена люди подключали вундер-проги сжатия HTTP-трафика, потому что протокол позволял. Прокси сами, на своих сетях устанавливали провайдеры, кэшируя запросы в пределах своих сетей. Не без причины провайдеры-же организовывали сервисы в интранете, экономя трафик и создавая внутригородской движ. Разумеется, популярностью пользовался Bittorrent внутри ISP.

          Это потом интернет стал быстрее и дешевле, государства наглее, а правоторговцы ярее. И лавочка прикрылась, а HTTP покрылся TLS.

          Я бы не отказался от кэша Steam (программа есть), дистрибутивов Linux (по HTTP) и других. Да, не всем поможет (у меня последняя миля - VDSL). Банальнейший пример: обновлять три системы на дистрибутиве с rolling release. Большая часть времени уходит на скачивание. Так что может и сам себе прокси поставлю.


  1. VADemon
    17.09.2024 20:15

    Проверка через v - v === 0 — это эффективный способ исключить NaN, так как для всех других чисел разница числа с самим собой всегда равна нулю.

    Нет, здесь также бесконечности отсекаются (зачем-то).