Урок по треку JavaScript 7 мин

Типы и приведение за 7 минут

Восемь типов, typeof, == и ===, правила приведения и ловушки сравнения.

В JavaScript у значения есть тип, а у переменной нет. Одна и та же переменная может хранить число, потом строку. Отсюда половина «странностей» языка: операторы сами приводят значения к нужному типу, и эти правила надо знать наизусть.

Восемь типов

Семь примитивов и объекты:

ТипПримерtypeof
undefinedundefined"undefined"
nullnull"object" (историческая ошибка)
booleantrue"boolean"
number42, NaN, Infinity"number"
bigint42n"bigint"
string'abc'"string"
symbolSymbol('id')"symbol"
object{}, [], функции, даты"object", для функций "function"

Примитивы неизменяемы и сравниваются по значению. Объекты сравниваются по ссылке: {} === {} это false, потому что это два разных объекта.

Когда вы пишете 'abc'.length, движок на мгновение оборачивает примитив в объект-обёртку String, читает свойство и выбрасывает обёртку. Поэтому у примитивов есть методы, но записать им свойство нельзя: s.x = 1 молча потеряется (в строгом режиме упадёт с TypeError).

Как точно узнать тип

  • typeof хорош для примитивов и функций, но typeof null === 'object' и typeof [] === 'object'.
  • value === null проверяет null.
  • Array.isArray(value) проверяет массив, в том числе пришедший из другого iframe.
  • Object.prototype.toString.call(value) даёт [object Date], [object RegExp] и так далее.
  • Number.isNaN(x) проверяет NaN без приведения. Глобальный isNaN('abc') сначала приводит строку к числу и вернёт true.

Три вида приведения

Любое неявное приведение сводится к трём операциям: к строке, к числу, к булеву.

К булеву. Ложных значений ровно восемь: false, 0, -0, 0n, '', null, undefined, NaN. Всё остальное истинно, включая '0', 'false', [] и {}.

К числу. Number('') и Number(null) дают 0, Number(undefined) даёт NaN, Number('12px') даёт NaN, Number(true) даёт 1, Number([]) даёт 0, Number([5]) даёт 5. Пробелы по краям строки игнорируются: Number(' 7 ') это 7.

К строке. String(null) это 'null', String([1, 2]) это '1,2', String({}) это '[object Object]'.

Объект сначала превращается в примитив: движок вызывает Symbol.toPrimitive, если он есть, иначе valueOf и toString (порядок зависит от подсказки: для чисел сначала valueOf, для строк сначала toString). У обычных объектов и массивов valueOf возвращает сам объект, поэтому в ход идёт toString.

const price = {
  valueOf() { return 100; },
  toString() { return 'сто'; },
};
console.log(price * 2);     // 200
console.log(`${price}`);    // 'сто'

Плюс и остальные операторы

Бинарный + особенный: если после приведения к примитиву хотя бы один операнд строка, выполняется конкатенация. Остальные арифметические операторы всегда приводят к числу.

1 + '2'      // '12'
'5' - 2      // 3
'5' * '2'    // 10
true + 1     // 2
[] + 1       // '1'   ([] -> '')
null + 1     // 1
undefined + 1 // NaN
1 + 2 + '3'  // '33'  (слева направо: 3 + '3')
'1' + 2 + 3  // '123'

Унарный плюс +x это короткая запись Number(x).

== против ===

=== сравнивает без приведения: разные типы сразу дают false. == сначала приводит:

  1. null == undefined это true, и больше ни с чем они нестрого не равны (null == 0 даёт false).
  2. Если один операнд число, а другой строка, строка приводится к числу.
  3. Булево всегда сначала превращается в число: true == '1' даёт true, а true == 'true' даёт false (1 == NaN).
  4. Объект против примитива: объект приводится к примитиву.
  5. NaN не равен ничему, даже себе.

Отдельно Object.is: как ===, но Object.is(NaN, NaN) это true, а Object.is(0, -0) это false.

Правило для работы: всегда ===. Единственное частое исключение: x == null ловит сразу null и undefined.

Сравнения < и >

Если оба операнда строки, они сравниваются посимвольно по кодам: '10' < '9' даёт true. Иначе оба приводятся к числу: '10' < 9 даёт false. Отсюда же ловушка null >= 0 это true, хотя null == 0 это false: сравнение приводит null к 0, а == для null особое.

Числа

Все number это 64-битные числа с плавающей точкой. Поэтому 0.1 + 0.2 !== 0.3, а целые точны только до Number.MAX_SAFE_INTEGER (2^53 - 1). Для больших целых есть bigint, но смешивать его с number в арифметике нельзя: 1n + 1 бросит TypeError.

parseInt('12px') читает число из начала строки и даёт 12, а Number('12px') даёт NaN. Передавайте parseInt основание: parseInt('08', 10).

Частые ошибки

  • Проверка if (count) там, где 0 это нормальное значение. Нужно count !== undefined или ??.
  • typeof x === 'object' как проверка на объект: пропустит null.
  • Сложение значений из input.value: это строки, '2' + '3' даст '23'.
  • arr.indexOf(NaN) всегда -1, а arr.includes(NaN) находит NaN.

Что спросят на собесе

  • Перечислите типы данных и почему typeof null равен 'object'.
  • Чем == отличается от ===, когда допустимо ==.
  • Какие значения ложные, во что превращаются [], {}, null, undefined при приведении к числу и строке.
  • Почему 0.1 + 0.2 !== 0.3 и как сравнивать дробные числа.
  • Как надёжно проверить, что значение массив, NaN или null.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.