Приведение типов (coercion) — часть языка с худшей репутацией: мемы про [] + {} знают даже те, кто на JavaScript не пишет. Но за «безумием» стоит небольшой набор правил, и тот, кто их знает, читает такой код спокойно — а главное, понимает, где приведение сработает молча и незаметно испортит данные.
Явное и неявное
Явное приведение — вы просите сами:
Number("42"); // 42
String(42); // "42"
Boolean(0); // false
parseInt("42px", 10); // 42 — парсит, пока может
Number("42px"); // NaN — а этот строг
Неявное — язык делает сам, когда типы не совпадают:
"5" - 1; // 4 — минус работает только с числами: строка → число
"5" + 1; // "51" — плюс при строке КОНКАТЕНИРУЕТ: число → строка
1 < 2 < 3; // true, но не потому, что вы думаете: (1<2)=true, true<3 → 1<3
Запомните асимметрию +: если хоть один операнд — строка, это склейка. Все остальные арифметические операторы тянут к числу. Отсюда классический баг: число из input.value (это всегда строка!) «складывается» с числом в "105" вместо 15.
truthy и falsy: короткий список лжи
В логическом контексте (if, !, &&, ||) каждое значение становится true или false. Falsy-значений ровно восемь — их проще выучить, чем угадывать:
false, 0, -0, 0n, "", null, undefined, NaN
Всё остальное — truthy. Включая неожиданное:
Boolean([]); // true — пустой массив truthy!
Boolean({}); // true
Boolean("0"); // true — непустая строка
Boolean("false"); // true
Отсюда грабли: if (items) {} не проверяет, что массив непуст, — он truthy всегда. Непустоту проверяют явно: if (items.length > 0).
== против ===: что на самом деле делает нестрогое сравнение
=== сравнивает без приведения: разные типы — сразу false. == приводит операнды к общему типу и сравнивает потом. Его правила короче, чем кажется:
- число == строка → строка приводится к числу;
- что-то == boolean → boolean приводится к числу (вот где ад:
"1" == true, потому что true → 1); - объект == примитив → объект приводится к примитиву (ToPrimitive:
valueOf, потомtoString); null == undefined→ true, и больше null ни с чем не равен.
"42" == 42; // true (строка → число)
"" == 0; // true ("" → 0)
"0" == false; // true (false → 0, "0" → 0)
[] == ""; // true ([] → "")
null == 0; // false! null равен только undefined
NaN == NaN; // false — NaN не равен ничему
Практическое правило команды: везде ===, единственное общепринятое исключение — x == null как компактная проверка «null или undefined». Это исключение настолько устоялось, что его понимают линтеры и коллеги.
Мемы, разобранные по правилам
[] + []; // "" — оба массива → toString → "" + ""
[] + {}; // "[object Object]" — "" + "[object Object]"
{} + []; // 0! — {} в начале строки парсится как БЛОК КОДА, остаётся +[] → 0
"2" * "3"; // 6 — умножение тянет к числам
Ни одна строчка здесь не случайна — всё следует из правил выше плюс одного факта про парсер (фигурные скобки в начале выражения — блок, а не объект). Писать так, конечно, не нужно; уметь прочитать — нужно.
Где приведение кусается в реальном коде
input.valueи данные из URL — всегда строки. Перед арифметикой и сравнением — явныйNumber().- Сортировка чисел:
[10, 9, 1].sort()даёт[1, 10, 9]— sort по умолчанию сравнивает строки. Нужен компаратор:sort((a, b) => a - b). - Проверки на «пусто»:
if (count)пропустит легитимный ноль. Если 0 — валидное значение, проверяйтеcount != nullилиcount !== undefined. - Ключи объектов — всегда строки:
obj[1]иobj["1"]— одно свойство.
Коротко
+со строкой склеивает, остальная арифметика тянет к числу.Number()строг,parseInt()парсит до первой ошибки.- Falsy всего восемь:
false, 0, -0, 0n, "", null, undefined, NaN. Пустые массив и объект — truthy. ==приводит типы по коротким правилам; boolean в сравнении сначала становится числом.null == undefined, и больше null не равен ничему.- Правило команды:
===везде,x == null— единственное исключение. - Реальные укусы: строковый
input.value, строковыйsort()по умолчанию,if (count)против нуля.
Что почитать дальше
- Типы в JavaScript — фундамент: какие типы вообще есть и где живёт NaN.
- Область видимости: var, let, const — следующая тема цикла.
- Основы JavaScript для TypeScript — как TypeScript закрывает часть этих граблей на этапе компиляции.