Преобразование типов в JavaScript
Узнайте, как JavaScript преобразует значения между string, числами и boolean — явные преобразования через String(), Number(), Boolean(), неявное приведение типов и распространённые ловушки.
JavaScript — динамически типизированный язык: переменная может хранить string в один момент и число в следующий, и вам никогда не нужно заранее объявлять тип значения. Из-за такой гибкости значения постоянно перемещаются между типами — число превращается в текст для вывода, текст из поля формы становится числом для вычислений, object проверяется на «истинность» внутри if. Эти преобразования происходят двумя способами: явно, когда вы сами вызываете функцию вроде String() или Number(), и неявно (также называемое приведением типов), когда язык автоматически преобразует значение, чтобы оно подходило для операции. В этой главе рассмотрены оба способа, точные правила для каждого целевого типа и неожиданности, которые рано или поздно застают врасплох почти каждого.
Если вы ещё не читали о доступных типах данных, полезно сначала бегло ознакомиться с ними — преобразования имеют смысл только тогда, когда вы знаете, между чем именно происходит конвертация.
Три вида преобразований
Почти каждое преобразование в JavaScript нацелено на один из трёх примитивных типов: string, число или boolean. Общей операции «преобразовать в тип X» не существует; вместо этого у каждого целевого типа есть собственные правила. Мы рассмотрим каждый по очереди, а затем разберём, когда язык применяет их за вас.
Преобразование в string
Преобразование в string превращает значение в его текстовую форму. Явно это делается с помощью функции String(), а неявно — когда значение встречается со string при конкатенации или интерполируется в шаблонный литерал.
String(123); // "123"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
`Value: ${42}`; // "Value: 42"Для примитивов правила интуитивны — значение отображается именно так, как вы ожидаете его прочитать. Обратите внимание, что null и undefined становятся буквальным текстом "null" и "undefined", а не пустой строкой.
С объектами и массивами всё интереснее. Array превращается в свои элементы, соединённые запятыми, а обычный object превращается в пресловутый "[object Object]":
Таким образом, пустой array при конвертации в string даёт пустую строку, а null/undefined внутри array отображаются как пустые слоты (поэтому [null, 5] даёт ",5"). Результат "[object Object]" для объектов — вот почему вы используете JSON.stringify(obj), когда действительно хотите прочитать содержимое объекта.
Числовое преобразование
Числовое преобразование превращает значение в число. Явная форма — функция Number(); неявно это происходит при унарном операторе плюс (+value) и с большинством арифметических и операторов сравнения operators.
Правила стоит запомнить, потому что они не всегда очевидны:
| Значение | Number(value) |
|---|---|
undefined | NaN |
null | 0 |
true / false | 1 / 0 |
"" (пустая строка) | 0 |
" 42 " (пробелы вокруг цифр) | 42 |
"42px" (нечисловое содержимое) | NaN |
"3.14" | 3.14 |
Некоторые из них печально известны. Number(null) равно 0, но Number(undefined) равно NaN — два «пустых» значения ведут себя по-разному. Пустая строка тоже становится 0, а не NaN. Строки сначала обрезаются от окружающих пробелов, затем строго разбираются; если остаётся что-то нечисловое, результатом будет NaN (что означает «Not a Number», то есть «не число»).
Унарный плюс
Унарный оператор плюс — самый короткий способ принудительно выполнить числовое преобразование. Поставленный перед значением, +value делает ровно то же самое, что и Number(value):
+"42"; // 42
+""; // 0
+true; // 1
+"abc"; // NaNЭто удобно, но легко может быть неправильно прочитано, поэтому используйте его там, где намерение очевидно (например, +event.target.value при чтении числового поля формы).
parseInt и parseFloat против Number
Number() строг: вся строка должна быть допустимым числом, иначе вы получите NaN. Функции parseInt() и parseFloat() более снисходительны — они читают строку с начала и останавливаются на первом символе, который не подходит, возвращая то, что успели разобрать.
Два предостережения относительно parseInt:
- Он возвращает
NaNтолько если строка не начинается с разбираемого числа —"px12"не работает, но"12px"даёт12. - Всегда передавайте основание (base) вторым аргументом, чтобы избежать сюрпризов:
parseInt("08", 10)надёжно даёт8. Без него современные движки по умолчанию используют основание 10, но явное указание устраняет любые сомнения и документирует ваше намерение.
parseInt("FF", 16); // 255 (hexadecimal)
parseInt("101", 2); // 5 (binary)
parseInt("08", 10); // 8 (decimal, explicit radix)Используйте Number() (или +), когда хотите проверить, что значение целиком является числом, и parseInt/parseFloat — когда намеренно хотите извлечь число из начала более длинной строки, например "24px" из CSS.
Преобразование в boolean
Преобразование в boolean отвечает на единственный вопрос: является ли это значение «истинным» (truthy) или «ложным» (falsy)? Явно это делается с помощью Boolean(), а неявно — везде, где вычисляется условие: внутри if, while, тернарного ? и логических операторов.
Правило освежающе простое, потому что список falsy-значений короткий и фиксированный. Эти восемь значений преобразуются в false:
false
0
-0
0n // BigInt zero
"" // empty string
null
undefined
NaNВсё остальное является truthy. Это «всё остальное» включает некоторые значения, которые удивляют новичков:
String "0" является truthy, потому что это непустая строка — её содержимое не имеет значения. Аналогично "false" — это вполне обычная непустая строка, и она преобразуется в true. А пустой array [] или пустой object {} являются truthy, даже если «кажутся» пустыми. Запомните список falsy-значений; если значения нет в нём, оно truthy.
Распространённая идиома для явного boolean — двойное отрицание: !!value. Первый ! преобразует в отрицательный boolean, второй переворачивает его обратно, давая тот же результат, что и Boolean(value), но короче. Например, !!"hello" равно true, а !!0 равно false.
Неявное приведение типов
До сих пор мы сами вызывали функции преобразования. Однако большинство реальных преобразований неявные — язык приводит значения, чтобы операция работала. Главное — знать, какой целевой тип ожидает каждый контекст.
Арифметика и особенность +
Каждый арифметический оператор приводит свои операнды к числам — за исключением +, который перегружен. Если хоть один операнд + является строкой, + становится конкатенацией строк; иначе он складывает числа. Все остальные операторы (-, *, /, %) всегда конвертируют в числа.
Строка 1 + 2 + '3' показывает, почему важен порядок: + является лево-ассоциативным, поэтому 1 + 2 выполняется первым как числа (3), затем 3 + '3' встречает строку и конкатенируется в "33". Это единственное правило — «+ с любой строкой конкатенирует, всё остальное конвертируется в число» — объясняет значительную часть путаницы у новичков.
Условия и логика
В любом boolean-контексте — if, while, ?: и логические операторы && / || — управляющее значение прогоняется через преобразование в boolean с использованием вышеуказанного списка falsy-значений.
if ("0") {
// runs: "0" is a non-empty string, therefore truthy
}
if (0 || "fallback") {
// "fallback" is returned: 0 is falsy, so || moves to the next value
}Сравнения
Операторы равенства выполняют собственное приведение типов, а оператор нестрогого равенства == в частности имеет настолько тонкие правила, что заслуживает отдельной главы. Кратко: отдавайте предпочтение строгому ===, который никогда не приводит типы. Полный набор неожиданностей (например, [] == ![] даёт true) рассмотрен в разделе операторы сравнения.
Преобразование object в примитив
Когда object используется там, где ожидается примитив (конкатенация, арифметика, сообщение alert), JavaScript сначала преобразует его в примитив. Для этого вызывается метод Symbol.toPrimitive объекта, если он существует; иначе используются valueOf() и toString(), порядок вызова которых зависит от того, ожидается ли число или строка. Именно поэтому обычный object превращается в "[object Object]", а array — в строку, соединённую запятыми — это поведение приходит из стандартного toString(). Вы можете настроить результат, определив эти методы на своих объектах, но для повседневного кода достаточно знать, что такое преобразование существует, и использовать JSON.stringify(), когда нужно читаемое представление объекта.
Распространённые ловушки
Несколько результатов преобразований порождают большинство моментов «подождите, что?». Держите их в уме:
Разберём странные случаи:
+[]равно0. Числовое преобразование сначала превращает array в string (""), затем пустая строка конвертируется в0.+{}равноNaN. Object становится"[object Object]", что не является допустимым числом, поэтому конвертируется вNaN."" + 1равно"1". Потому что+встретил строку и выполнил конкатенацию вместо сложения.[] + []равно"". Каждый array преобразуется в пустую строку, а"" + ""даёт"".
Это не ошибки — они точно следуют правилам, описанным выше. Они лишь кажутся странными, потому что в одном выражении последовательно применяются два разных преобразования.
Лучшая практика: отдавайте предпочтение явному преобразованию. Запись Number(input), String(value) или Boolean(flag) делает ваше намерение очевидным для следующего читателя и обходит неожиданности неявного приведения типов. Используйте неявное преобразование только в очевидных случаях и применяйте строгое ===, чтобы значения никогда не изменялись незаметно при сравнениях.
Итоги
- JavaScript преобразует значения в string, число или boolean — явно через
String(),Number(),Boolean(), или неявно через приведение типов. - String: примитивы отображаются как текст;
[1,2,3]становится"1,2,3",[]становится"", а обычный object становится"[object Object]". - Число:
nullравно0,undefinedравноNaN,""равно0; строки обрезаются, затем строго разбираются. Используйте+valueкак сокращение иparseInt/parseFloat(с основанием) для нестрогого разбора начала строки. - Boolean: только
false,0,-0,0n,"",null,undefinedиNaNявляются falsy; всё остальное — включая"0","false",[]и{}— является truthy. +конкатенирует, если одна из сторон является строкой; каждый другой арифметический оператор конвертирует в число.- Если сомневаетесь — конвертируйте явно и сравнивайте через
===.
Вы будете постоянно использовать эти правила при работе с числами, строками и пользовательским вводом, хранящимся в переменных.