Синтаксис, типы и приведение
JavaScript изначально кажется простым языком: можно написать почти
что угодно, и оно как-нибудь да заработает. Деление на ноль не падает,
а возвращает Infinity. Сравнение 1 == "1" оказывается правдой.
[] + {} тихо превращается в строку, null + 1 отдаёт единицу, а
typeof null упорно показывает 'object'. Там, где другие языки
кричат об ошибке компиляции, JS пожимает плечами и выдаёт результат,
не всегда тот, который ты ожидал.
Эта снисходительность языка и есть главная ловушка. Пока ты пишешь
на JS по привычкам из C, Python или Go, рассчитывая, что компилятор
поймает опечатки, JS будет молча соглашаться и подкидывать тихие
сюрпризы. 0.1 + 0.2 не равно 0.3. Строка '10' “меньше” строки
'9'. Пустой массив истинный при приведении к булеву, но равен нулю
через ==. Это не баги, всё это документированное поведение,
описанное в спецификации. Просто оно требует, чтобы ты знал, как
именно язык читает выражения и как один тип превращается в другой.
Этот урок собирает базис. Мы пройдём по семи примитивам и научимся
отличать их от объектов, разберём IEEE 754 и поймём, откуда берутся
хвостовые ошибки в дробных числах, посмотрим, что именно возвращают
&&, || и ??, чем == отличается от === и когда нужен
Object.is, разложим по полочкам for...of, for...in и switch.
Этого достаточно, чтобы перестать гадать и начать читать чужой код
буквально.
Дальше серия достраивает остальное: функции и замыкания во втором уроке, объекты и прототипы в третьем, ФП-стиль в четвёртом, коллекции и итерационные протоколы в пятом, строки и Unicode в шестом, ошибки и события в седьмом, метапрограммирование и V8 в восьмом.
1. Откуда брать JS
Минимум три способа запустить любой пример из урока.
# 1) Node REPL (Active LTS, на 2026 это 22.x)
$ node
> 1 + 2
3
# 2) одной строкой
$ node -e 'console.log(0.1 + 0.2)'
0.30000000000000004
# 3) файлом
$ echo 'console.log(typeof null)' > scratch.js
$ node scratch.js
object
В браузере открой DevTools (Cmd+Opt+I в Chrome) и пиши в Console.
Поведение ровно то же, кроме того, что в браузере есть window,
document, localStorage, а в Node: process, Buffer,
require/import. Браузерный API трогаем в разделе 07, серверный, в
разделе 06.
2. Семь примитивов
Всё, что не объект, в JS примитив. Их семь:
| Тип | Литерал | typeof |
|---|---|---|
| number | 42, 0.1, Infinity, NaN | number |
| bigint | 42n, 2n ** 64n | bigint |
| string | 'hi', "hi", `hi` | string |
| boolean | true, false | boolean |
| symbol | Symbol('id') | symbol |
| null | null | object (историческая ошибка) |
| undefined | undefined | undefined |
Объект не примитив. Массив, функция, дата, регулярка, Map, Set, всё
это объекты с typeof === 'object' (функция отдельно, у неё
typeof === 'function').
typeof 42; // 'number'
typeof 42n; // 'bigint'
typeof 'hi'; // 'string'
typeof true; // 'boolean'
typeof Symbol(); // 'symbol'
typeof null; // 'object' <- баг 1995 года, пофиксить уже нельзя
typeof undefined; // 'undefined'
typeof {}; // 'object'
typeof []; // 'object'
typeof (() => {}); // 'function'
null показывает 'object' потому, что в первой реализации движка
тег типа считывался по младшим битам, и у null (нулевой указатель)
тег совпал с тегом объектов. Поправить нельзя: ломает существующий
код в интернете.
Примитивы передаются по значению, объекты по ссылке. Подробнее в уроке
3 (03-objects-arrays-prototypes), пока запомни: присваивание
примитива копирует, присваивание объекта копирует только ссылку.
let a = 1;
let b = a;
b = 2;
console.log(a); // 1, копия
const o1 = { x: 1 };
const o2 = o1;
o2.x = 2;
console.log(o1.x); // 2, та же ссылка
3. Number и IEEE 754
В JS только один числовой тип, IEEE 754 double.
Целые до 2 ** 53 - 1 представлены точно, дальше пропуски. Дробные с
конечной двоичной записью точны (0.5, 0.25, 0.125), остальные,
бесконечные дроби в двоичной системе, как 0.1 или 0.2.
0.1 + 0.2 === 0.3; // false
0.1 + 0.2; // 0.30000000000000004
Number.MAX_SAFE_INTEGER; // 9007199254740991
2 ** 53 === 2 ** 53 + 1; // true, точность кончилась
Поэтому в финансах и идентификаторах храним целые в копейках/центах или используем BigInt:
const orderId = 9007199254740993n; // ровно столько, без потерь
orderId + 1n; // 9007199254740994n
orderId + 1; // TypeError: Cannot mix BigInt and other types
Number(orderId); // 9007199254740992 (потеряли единицу)
Сравнение дробных через эпсилон:
const equalsApprox = (a, b, eps = Number.EPSILON) =>
Math.abs(a - b) < eps;
equalsApprox(0.1 + 0.2, 0.3); // true
NaN, особый случай:
NaN === NaN; // false, единственное значение, не равное себе
Number.isNaN(NaN); // true
isNaN('abc'); // true (приводит к Number, потом проверяет)
Number.isNaN('abc'); // false (никаких приведений)
Запомни: Number.isNaN без приведения, isNaN глобальный с приведением.
Используй Number.isNaN, Number.isFinite, Number.isInteger, чтобы
не нарваться на скрытое приведение.
4. String, кратко
Строка в JS, последовательность UTF-16 кодовых единиц. Это значит,
что "😀".length === 2, потому что emoji занимает две кодовые
единицы (surrogate pair). Подробно про Unicode и Intl в уроке 6
(06-strings-regex-unicode). Пока минимум:
'a' + 'b'; // 'ab'
`x = ${1 + 2}`; // 'x = 3', шаблонная строка
'abc'.length; // 3
'😀'.length; // 2
[...'😀'].length; // 1, итерация по code points
Литералы взаимозаменяемы, ', ", `. Шаблонные литералы дают
интерполяцию ${expr} и многострочность. Конкатенация через +
работает, но в новом коде используй шаблонные.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.