← назад к разделу

Приведение типов (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 закрывает часть этих граблей на этапе компиляции.