Learning Book

Как работает JavaScript

Интерпретируемый или компилируемый?

JavaScript часто называют «интерпретируемым» языком — то есть код читается и выполняется строчка за строчкой, без предварительной компиляции. Это правда, но не вся правда.

Компилируемые языки (C, Go, Rust) — код преобразуется в машинный код заранее. Бинарник запускается без исходника.

Интерпретируемые языки — интерпретатор читает исходный код и выполняет его напрямую.

JavaScript использует подход JIT (Just-In-Time compilation) — гибрид. Код компилируется в момент выполнения, прямо в машинные инструкции, что даёт скорость близкую к скомпилированным языкам.

Движок JavaScript

Движок JavaScript — это программа, которая читает JS-код и выполняет его. Каждый браузер имеет свой движок:

Браузер / СредаДвижок
Chrome, Edge, Node.jsV8 (Google)
FirefoxSpiderMonkey (Mozilla)
SafariJavaScriptCore / Nitro (Apple)

Все движки реализуют стандарт ECMAScript, поэтому один и тот же JS-код работает везде (почти — бывают различия в нестандартных API).

Что происходит с кодом

Когда браузер встречает <script>, движок проходит несколько этапов:

Исходный код
      ↓
   Parsing         — разбор текста, построение AST
      ↓
   Compilation     — компиляция в байткод (Ignition в V8)
      ↓
   Execution       — выполнение
      ↓
   Optimization    — горячий код переоптимизируется TurboFan

Для разработчика это прозрачно — просто пишешь код, движок всё делает сам.

// Этот код проходит через все этапы автоматически
function add(a, b) {
  return a + b
}

// Если функция вызывается тысячи раз — движок её оптимизирует
for (let i = 0; i < 10000; i++) {
  add(i, i + 1)
}
AST (Abstract Syntax Tree) — это дерево, которое представляет структуру кода. Например, 2 + 3 превращается в узел «BinaryExpression» с дочерними узлами «2» и «3». Именно с AST работают линтеры, форматтеры и компиляторы TypeScript.

Event Loop — основа асинхронности

JavaScript однопоточный — одновременно выполняется только одна операция. Но он может обрабатывать сетевые запросы, таймеры, события — всё это благодаря Event Loop.

console.log('1 — начало')

setTimeout(() => {
  console.log('3 — таймер выполнился')
}, 0)

console.log('2 — конец основного кода')

// Вывод:
// 1 — начало
// 2 — конец основного кода
// 3 — таймер выполнился

Таймер с 0 мс всё равно выполняется после основного кода — он ждёт в очереди.

Подробнее о Event Loop — в главе про асинхронность. Здесь важно понять одно: JS однопоточный, но не блокирующий.
V8 использует двухуровневую компиляцию. Сначала **Ignition** (интерпретатор байткода) быстро компилирует и запускает код. Затем профайлер отслеживает «горячие» функции — те, что вызываются часто. Их передаёт **TurboFan** (оптимизирующий компилятор), который генерирует высокооптимизированный машинный код. Если предположения TurboFan нарушаются (например, функция вдруг получила другой тип), происходит «деоптимизация» — код возвращается к Ignition. Подробнее — в главе про движок V8.