В JavaScript «у переменных нет типов — типы есть у значений». Переменная может хранить сегодня строку, завтра число, и это законно. Поэтому понимать систему типов здесь важнее, чем в строгих языках: она не проверяется компилятором, а работает прямо во время выполнения — и удивляет тех, кто её не знает.
Семь примитивов и всё остальное
Примитивные типы: string, number, boolean, undefined, null, symbol, bigint. Всё остальное — объекты: обычные объекты, массивы, функции, даты, регулярные выражения.
typeof "hi"; // "string"
typeof 42; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol(); // "symbol"
typeof 10n; // "bigint"
typeof {}; // "object"
typeof []; // "object" — массив тоже объект!
typeof function(){} // "function" — особый ответ для функций
typeof null; // "object" — знаменитый баг языка
Два ответа typeof требуют комментария. typeof null === "object" — историческая ошибка первой реализации, которую нельзя исправить, не сломав интернет: проверяйте null сравнением x === null. А "function" — любезность: функции формально объекты, но typeof выделяет их отдельно.
Массив от объекта typeof не отличит — для этого есть Array.isArray():
Array.isArray([]); // true
Array.isArray({}); // false
undefined и null: два вида «ничего»
В языке два «пустых» значения, и по конвенции они означают разное:
undefined— «значения ещё не было»: необъявленное свойство, параметр без аргумента, функция безreturn.null— «значение намеренно пустое»: его присваивают явно, обозначая осознанное отсутствие.
let user;
user; // undefined — не присвоили
const found = null; // null — искали и осознанно не нашли
Практическое следствие: если в данных вы видите undefined — скорее всего, кто-то забыл присвоить или опечатался в имени свойства; null — кто-то положил пустоту специально. Проверка «нет ни того ни другого» одной строкой: x == null — редкий случай, когда нестрогое сравнение осознанно полезно.
number: один тип на всё и NaN
В JavaScript нет отдельных целых и дробных — только number (64-битный float). Отсюда две классики:
0.1 + 0.2 === 0.3; // false! 0.30000000000000004
Number.MAX_SAFE_INTEGER; // 9007199254740991 — дальше целые «врут»
Дробные деньги не считают в number «в лоб» — считают в копейках или через строковые библиотеки; для гигантских целых есть bigint.
NaN («not a number») — результат бессмысленной числовой операции. Его коварство: NaN — единственное значение, не равное самому себе:
NaN === NaN; // false
Number.isNaN(NaN); // true — правильная проверка
Если сравнение x === x даёт false — перед вами NaN. Это не шутка, а реальный приём из старого кода.
Значение против ссылки
Главная развилка поведения: примитивы копируются по значению, объекты — по ссылке.
let a = 1;
let b = a;
b = 2;
a; // 1 — копия независима
const x = { n: 1 };
const y = x;
y.n = 2;
x.n; // 2! — x и y указывают на ОДИН объект
Это фундамент, на котором стоят и копирование объектов, и правила изменения состояния в React. Пока держите в голове само правило: присваивание объекта не создаёт новый объект — только вторую ссылку на тот же.
Упаковка: почему у строки есть методы
Строка — примитив, но "hi".toUpperCase() работает. Как? При обращении к свойству примитива движок на мгновение оборачивает его в объект (String, Number, Boolean), вызывает метод и выбрасывает обёртку. Отсюда правило: обёртки существуют, но создавать их руками (new String("hi")) не нужно никогда — вы получите объект со всеми сюрпризами объектного поведения:
typeof new String("hi"); // "object", не "string"
new Boolean(false) ? 1 : 2; // 1! объект всегда truthy
Коротко
- Типы у значений, не у переменных. Семь примитивов, всё остальное — объекты (массивы и функции тоже).
typeof null === "object"— баг языка; null проверяют через=== null, массивы — черезArray.isArray().undefined— «не присвоили»,null— «пусто намеренно».x == nullловит оба.- Один числовой тип:
0.1 + 0.2 !== 0.3, деньги — в копейках.NaNне равен сам себе, проверка —Number.isNaN(). - Примитивы копируются по значению, объекты — по ссылке. Это причина половины «мистических» багов.
- Обёртки примитивов работают автоматически;
new String()руками — не надо.
Что почитать дальше
- Приведение типов — что происходит, когда типы встречаются в одном выражении.
- Объекты: ссылки, копирование и неизменяемость — продолжение темы «по значению vs по ссылке».
- Основы JavaScript для TypeScript — как поверх этой системы типов встаёт статическая типизация.