Два вида приведения типов в JavaScript
JavaScript довольно вольно обращается с типами. Если оператор получает значение «не того» типа, язык не падает с ошибкой — он просто преобразует его. Это и называется приведением типов (coercion), и бывает оно двух видов:
- Явное приведение типов — вы сами просите о конвертации:
Number("42"),String(99),Boolean(value). - Неявное приведение типов — его запускает сам оператор, без вашего участия:
"5" - 2,"" == 0,if (value).
Под капотом и то, и другое дергает одни и те же правила преобразования. Разница только в том, кто принял решение конвертировать — вы или движок. И практически все странности JS, от которых встают волосы дыбом — "5" + 1 === "51", [] == false, null == undefined — растут именно из неявного приведения, которое срабатывает в самый неожиданный момент.
Запомните простую модель: когда вы пишете Number(...) или String(...) — вы чётко говорите, что вам нужно. А если нет, то у каждого оператора своё мнение о том, как преобразовать значения, — и вот именно в этих «мнениях» и прячутся баги.
Три целевых типа
Приведение типов в javascript всегда идёт к одному из трёх примитивных типов: string, number или boolean. (Есть ещё четвёртый — bigint, — но в него автоматически из других типов ничего не приводится.) Все остальные правила вытекают из того, к какому из этих типов «целится» оператор.
Приведение к строке — самая «добрая» операция: у каждого значения есть своё строковое представление. Обратите внимание, что объекты превращаются в бесполезное "[object Object]" — именно поэтому, когда вы выводите в консоль объект, склеенный со строкой ("user: " + user), вы почти никогда не видите того, что хотели. Вместо этого используйте JSON.stringify или шаблонные строки с конкретными полями.
Преобразование строки в число в JS
Приведение к числу работает куда строже. Строка должна действительно выглядеть как число, иначе получите NaN:
Из сюрпризов в скобках стоит запомнить вот что:
- Пустая строка и строка из одних пробелов превращаются в
0, а не вNaN. nullстановится0, аundefined— ужеNaN.- Пустой массив даёт
0; массив из одного элемента приводит этот самый элемент; массив из нескольких элементов превращается вNaN.
Если нужно вытащить число из строки с посторонними символами, берите parseInt или parseFloat вместо Number:
Всегда передавайте основание системы счисления (10) в parseInt. Без него строки, начинающиеся с "0x", будут распарсены как шестнадцатеричные, а это вряд ли то, чего вы хотели.
Приведение к булевому типу в JavaScript
Приведение к Boolean — самое простое из трёх. Есть короткий список значений, которые превращаются в false, а всё остальное становится true.
К ложным (falsy) значениям относятся:
false0,-0,0n""(пустая строка)nullundefinedNaN
И всё. Любое другое значение — включая "false", "0", [] и {} — считается истинным (truthy).
Пустые массивы и объекты часто сбивают с толку тех, кто пришёл из Python, где пустые коллекции — это false. В JavaScript всё наоборот: они truthy. Так что если нужно проверить «массив пустой?», пишите явно: arr.length === 0.
Приведение к булевому типу js происходит каждый раз, когда значение оказывается там, где ожидается boolean: в if (...), while (...), тернарнике ? : и логических операторах &&, ||, !.
Оператор + — особый случай
Большинство арифметических операторов принудительно преобразуют операнды в числа. А вот + ведёт себя иначе: если хотя бы один из операндов — строка, + склеивает строки. В остальных случаях это обычное числовое сложение.
Вычисление идёт слева направо. В выражении 1 + 2 + "3" сначала считается 1 + 2 = 3, а потом 3 + "3" = "33". А вот "1" + 2 + 3 сразу «уходит в строку» и остаётся там: сначала "12", потом "123".
Именно поэтому склеивать строки через + — затея ненадёжная. В шаблонных строках такой проблемы нет:
Шаблонная строка сначала вычисляет count + 1 как обычное числовое выражение, а уже потом подставляет результат. Никакого неявного приведения типов.
Лучше использовать явное приведение типов
Если вам действительно нужно преобразовать значение — сделайте это явно. Пара лишних символов, зато следующий читатель кода не будет гадать, что здесь происходит:
Та же история с булевыми значениями. !!value работает и часто встречается в коде, но Boolean(value) прямо говорит, что именно происходит:
Разумное правило: в прикладной логике используйте явное приведение типов, а короткие формы (+x, !!x) оставьте для тех мест, где важна лаконичность и намерение понятно из контекста.
Оператор == активно полагается на приведение типов
Операторы сравнения — главный источник сюрпризов, связанных с приведением типов. == приводит типы перед сравнением, а === — нет.
Каждый true выше — результат цепочки неявных преобразований из нескольких шагов, которую большинство разработчиков просто не держит в голове. И в этом вся беда: код, который работает случайно, рано или поздно сломается. Полные правила сравнения мы разберём на следующей странице, а пока запомните главное: по умолчанию используйте ===, а == оставьте только для одной идиомы — x == null (она ловит сразу и null, и undefined).
Собираем всё вместе
Разберём пример, где приведение типов выручает, а где — только мешает:
Обрати внимание на второй вызов. Number("") возвращает 0, а не NaN — значит, parsePrice("") вернёт 0, чего вызывающий код от этой функции вряд ли ожидает. Если пустую строку нужно отбрасывать, добавь явную проверку:
Знание того, какие значения приводятся к 0, а какие — к NaN, — это как раз то, что спасёт вас от неуловимого бага где-то дальше по коду.
Что запомнить
- Приведение типов преобразует значение в строку, число или булево — в зависимости от оператора.
+с любой строкой превращается в конкатенацию; все остальные арифметические операторы приводят к числу.- Falsy-значений в JavaScript ограниченный список — выучите его наизусть. Всё остальное truthy, включая
[]и{}. Number("")даёт0,Number([])даёт0,Number(null)даёт0— а вотNumber(undefined)ужеNaN. На таких мелочах и ловятся реальные баги.- Используйте явное приведение через
Number(x),String(x),Boolean(x)вместо хитрых неявных трюков. Будущий вы скажет спасибо.
Дальше: операторы равенства
Всё, что приведение типов делает при сравнениях, спрятано внутри ==. На следующей странице разберём == vs === в javascript, а также Object.is, единственный случай, где == всё ещё уместен, и почему линтеры по умолчанию ругаются на нестрогую форму.
Часто задаваемые вопросы
Что такое приведение типов в JavaScript?
Это когда JavaScript сам преобразует значение из одного типа в другой — число в строку, строку в число или что угодно в булево. Неявно это происходит, когда операторы вроде +, == или if получают не тот тип, который ожидали. Явно — когда вы сами вызываете Number(x), String(x) или Boolean(x).
Чем отличается неявное приведение от явного?
Явное — это когда вы осознанно вызываете функцию преобразования: Number("42"), String(99), Boolean(value). Неявное — когда оператор сам дёргает преобразование за вашей спиной: "5" - 2 даст 3, а "5" + 2 уже "52". Явное читается понятно и предсказуемо, а неявное — главный источник багов в духе «откуда тут вообще взялся NaN».
Как преобразовать строку в число в JavaScript?
Для строгого преобразования используйте Number("42") — если строка не является корректным числом, вернётся NaN. Если нужно вытащить число из начала «грязной» строки, подойдут parseInt("42px", 10) или parseFloat("3.14em"). Унарный + (например, +"42") работает так же, как Number(), но его легко не заметить при беглом чтении кода.
Почему [] + [] возвращает пустую строку?
[] + [] возвращает пустую строку?У оператора + нет осмысленной числовой операции для двух массивов, поэтому JavaScript приводит оба к строкам. Массив превращается в строку через соединение элементов запятыми, а пустой массив превращается в "". В итоге [] + [] становится "" + "", то есть "". Забавный трюк и хороший повод никогда не использовать + с непримитивами.