Движок и инструменты
Написанный вами JavaScript почти никогда не исполняется дословно. Сначала его берёт движок (V8 в Chrome и Node) — разбирает в AST, компилирует в байт-код, интерпретирует и на ходу перекомпилирует горячие участки JIT-компилятором в машинный код. Вокруг движка стоит среда выполнения (браузер, Node, Deno), которая даёт хост-API — DOM, таймеры, файловую систему, fetch — и, что важно, сам событийный цикл. А до попадания в браузер исходник проходит конвейер сборки: транспиляцию, объединение модулей, tree-shaking, минификацию и внедрение полифилов.
Три границы стабильно проверяют на собеседовании. Первая — движок против среды выполнения: событийный цикл и fetch принадлежат среде, а не движку; отсюда и ReferenceError на document в Node. Вторая — транспиляция против полифилов: первая переписывает синтаксис, вторая добавляет отсутствующий API, и одно не заменяет другое. Третья — статический ESM против динамического CommonJS: tree-shaking возможен только по статически известному графу импортов. Каждый механизм разобран в слоях ниже.
Карта темы
- Движок JavaScript — разбор, байт-код, JIT, скрытые классы и инлайн-кэш, а также где живёт сборщик мусора.
- Среды выполнения — чем различаются браузер, Node и Deno по хост-API и кому принадлежит событийный цикл.
- Модульные системы — CommonJS против ES-модулей, живые привязки и совместимость через динамический
import(). - Транспиляция — что делает
Babel, чем транспиляция синтаксиса отличается от полифилов API. - Сборка и tree-shaking — сборщик, минификация, разделение кода, source map и почему tree-shaking требует статического ESM.
- Полифилы — реализация отсутствующего API, отличие от shim и определение возможностей в рантайме.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Помещать событийный цикл внутрь движка | Он принадлежит среде выполнения; отсюда путаница в объяснении асинхронности |
| Считать JS чисто интерпретируемым | Игнорируется JIT — горячий код компилируется в машинный |
| Смешивать транспиляцию и полифилы | Babel не добавит Promise сам — нужен core-js; синтаксис и API — разные слои |
| Ждать tree-shaking от CommonJS | Динамический require с вычисляемым путём ломает статический анализ |
| Поставлять всю библиотеку полифилов | Раздутый бандл вместо лишь отсутствующих у целей API |
Путать setImmediate и setTimeout(0) | Разные фазы цикла; порядок на верхнем уровне не гарантирован |
| Считать, что source map меняет поведение | Это лишь подспорье для отладки, поставляемый код не меняется |
Значение для собеседований
Тему спрашивают, чтобы отделить тех, кто «просто пишет код», от тех, кто понимает, как он выполняется и собирается. Кандидат, который говорит «событийный цикл — это среда выполнения, а не движок; движок даёт стек вызовов и кучу», сразу выглядит сильнее заучившего «JavaScript однопоточный».
Что обычно проверяют:
- Этапы работы движка и почему JIT ускоряет горячий код.
- Чем браузер, Node и Deno различаются по API и что общего (движок).
- Разницу CommonJS и ESM и почему tree-shaking возможен только для ESM.
- Что транспиляция преобразует синтаксис, а полифил добавляет API.
Типичный неверный ответ: «Babel полифилит Promise и Map». Babel — транспилятор синтаксиса; отсутствующие API подтягивает core-js. Смешение этих двух ролей — самая частая ошибка в этой теме.