Как работает JavaScript
Интерпретируемый или компилируемый?
JavaScript часто называют «интерпретируемым» языком — то есть код читается и выполняется строчка за строчкой, без предварительной компиляции. Это правда, но не вся правда.
Компилируемые языки (C, Go, Rust) — код преобразуется в машинный код заранее. Бинарник запускается без исходника.
Интерпретируемые языки — интерпретатор читает исходный код и выполняет его напрямую.
JavaScript использует подход JIT (Just-In-Time compilation) — гибрид. Код компилируется в момент выполнения, прямо в машинные инструкции, что даёт скорость близкую к скомпилированным языкам.
Движок JavaScript
Движок JavaScript — это программа, которая читает JS-код и выполняет его. Каждый браузер имеет свой движок:
| Браузер / Среда | Движок |
|---|---|
| Chrome, Edge, Node.js | V8 (Google) |
| Firefox | SpiderMonkey (Mozilla) |
| Safari | JavaScriptCore / 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)
}
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 мс всё равно выполняется после основного кода — он ждёт в очереди.