Число — примитивный тип Number в JavaScript. Под одним именем тут живут и целые (42, -7), и дробные (3.14), и шестнадцатеричные (0xff), и экспоненциальные (1e3). Технически все они хранятся в одном формате — 64-битное число с плавающей точкой по стандарту IEEE 754. Из-за этого, например, 0.1 + 0.2 возвращает не 0.3, а 0.30000000000000004, — но это уже отдельная история.
let count = 42;
let price = 1999.99;
let hex = 0xff; // 255
let exp = 1e3; // 1000
let negative = -273.15;
В чистом виде число встречается реже, чем кажется — в реальном коде оно почти всегда либо приходит откуда-то снаружи, либо уходит куда-то наружу. И вот тут начинаются преобразования.
Откуда в коде появляются числа в «неправильной» форме
Несколько типичных мест, где значение приходит как число, но не является числом с точки зрения JavaScript:
- Формы. Любой <input type="number"> возвращает в JS строку. То есть document.querySelector('input').value — это "42", а не 42. Если сложить такую «цифру» с другой через +, получится конкатенация: "42" + "10" === "4210".
- URL и query-параметры. URLSearchParams.get('page') отдаёт "3", не 3.
- localStorage / sessionStorage. Хранилища знают только строки. Положил число — забрал строку.
- Старые API. Некоторые JSON-ответы (особенно от старых бэкендов) присылают цифры в кавычках, чтобы избежать потери точности при больших значениях вроде ID-шников.
- CSS-значения через JS. getComputedStyle(el).width вернёт "320px" — чтобы посчитать что-то, придётся распарсить.
А ещё число постоянно нужно конвертировать в другую сторону — например, в строку перед записью в DOM или в булево для проверки в if.
Все пять направлений преобразования
Из числа можно сделать любой другой тип. Каждый случай разобран в отдельной главе — здесь короткий обзор, чтобы понимать, что куда ведёт.
- Число в строку — самый частый случай: вывод в шаблон, конкатенация, форматирование. Способов три: String(num), num.toString(), `${num}`. Внутри они почти равны, но toString() ещё умеет брать систему счисления вторым аргументом — (255).toString(16) вернёт "ff".
- Число в булевое — Boolean(num) или короткое !!num. Тут единственная странность: NaN считается false, как и 0 с -0. Всё остальное (включая Infinity) — true.
- Число в массив — задача обычно сводится к «посчитать цифры в числе»: [...String(num)] или String(num).split(''). Если нужны именно цифры как числа, в конце ещё .map(Number).
- Число в объект — редкий кейс, в основном для оборачивания через new Number(num) (объект-обёртка). В современном коде почти не нужен — примитивы и так автоматически «боксятся» при вызове методов вроде (42).toFixed(2).
- Число в специальное значение — null, undefined, NaN. Делается тернарником или if: когда число логически «пустое» или «неизвестное».
Явное и неявное преобразование — и почему первое лучше
JavaScript часто приводит числа автоматически — это называется неявным (или имплицитным) преобразованием. Например, оператор + в выражении "3" + 1 сложит как строки и вернёт "31". А "3" - 1 — уже как числа, вернёт 2. Логично? Не очень.
"3" + 1 // "31" (плюс: конкатенация)
"3" - 1 // 2 (минус: вычитание)
"3" * 2 // 6 (звёздочка: умножение)
"3" / 1 // 3 (слэш: деление)
+"3" // 3 (унарный плюс — каст в число)
"5" == 5 // true (== делает каст; === не делает)
Эти приведения короткие и порой удобные, но в команде их быстро забывают и читают неправильно. Лучше писать явно: Number(x), parseInt(x, 10), parseFloat(x). Тогда соседу по коду не приходится разгадывать, какое преобразование тут произойдёт.
Маленький лайфхак: если нужно превратить строку в число и не важно, целое или дробное, — пиши Number(x). Если важно отрезать единицы измерения ("320px"), — parseInt(x, 10). Второй аргумент 10 — основание системы счисления; без него parseInt может сюрпризно интерпретировать "0x10" как шестнадцатеричное.
Главные подводные камни
Несколько граблей, на которые наступали все, кто работал с числами в JS.
NaN — это число. Самое странное: typeof NaN === "number". Это «не число», которое числом и является. NaN не равен сам себе (NaN === NaN — false), и любая арифметика с ним даёт снова NaN. Проверять надо через Number.isNaN(x) — именно эту функцию, а не глобальную isNaN(): глобальная сначала приводит аргумент к числу, и isNaN("hello") вернёт true, что обычно не то, чего ожидаешь.
Number("hello") // NaN
Number("") // 0 (пустая строка → ноль, не NaN)
Number(null) // 0 (null → ноль; внезапно)
Number(undefined) // NaN (undefined → не число)
Number(true) // 1
Number(false) // 0
Number([]) // 0 (пустой массив → ноль)
Number([42]) // 42 (массив из одного элемента → этот элемент)
Number([1, 2]) // NaN (массив с двумя → NaN)
Большие целые — не всегда точные. Безопасный диапазон целых чисел — от -(2⁷³ - 1) до 2⁷³ - 1 (от Number.MIN_SAFE_INTEGER до Number.MAX_SAFE_INTEGER). Если ID базы пришёл больше — используй BigInt, не Number.
Числа с ведущим нулём. parseInt("08") в современных движках вернёт 8, а в очень старых — 0 (восьмеричная система). Чтобы не было сюрпризов на легаси, всегда указывай второй аргумент: parseInt("08", 10).