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

В 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 — как поверх этой системы типов встаёт статическая типизация.