Назад ко всем статьям
Технологический обзор

JavaScript под капотом: Движок V8, Контекст выполнения (Execution Context) и Архитектура памяти

От абстрактного синтаксического дерева (AST) до машинного кода: как устроен движок V8, почему возникает деоптимизация и как браузер распределяет память между стеком и кучей.

E

Elgun Mammadli

Front End Developer

Oct 7, 2026
6 минут
JavaScript под капотом: Движок V8, Контекст выполнения (Execution Context) и Архитектура памяти

Большинство разработчиков начинают знакомство с JavaScript с console.log("Hello World"), объявления переменных или написания простых функций. Однако переход на уровень Senior требует понимания процессов, скрытых за синтаксисом: что на самом деле представляет наш код для машины и как движок выполняет его на физическом оборудовании?

В этой статье мы подробно разберем внутреннюю архитектуру JavaScript: конвейер оптимизации движка V8, жизненный цикл контекста выполнения (Execution Context) и низкоуровневые принципы управления памятью.


1. Анатомия движка: От исходного кода до машинных инструкций

JavaScript давно перестал быть чисто интерпретируемым языком; современные среды используют JIT-компиляцию (Just-In-Time). Наиболее распространенным движком является V8 от Google (он используется в Chrome, Node.js и Deno).

Попадая в движок, код проходит через следующий многоступенчатый конвейер:

pipeline-architecture.txt
Исходный код 
    │
    ▼
[ Парсер / Сканер ]  ──►  Поток токенов и AST (дерево)
    │
    ▼
[ Ignition ] (Интерпретатор) ──►  Байт-код  ──► Выполняется
    │
    ├─► Профайлер (Анализ типов и "Hot Code")
    │
    ▼
[ TurboFan ] (Оптимизирующий компилятор) ──► Оптимизированный машинный код

Парсер и AST (Абстрактное синтаксическое дерево)

Движок начинает с лексического анализа: исходный текст разбирается на элементарные единицы — токены. Затем синтаксический анализатор строит на их основе AST (Abstract Syntax Tree) — иерархическую древовидную структуру, отражающую грамматику программы.

Ignition (Интерпретатор)

Интерпретатор V8, Ignition, принимает AST и генерирует компактный, независимый от платформы байт-код. Этот код запускается мгновенно без задержек на сложную компиляцию, что обеспечивает быстрый старт приложения.

TurboFan и Профайлер (JIT-оптимизация)

Параллельно с выполнением байт-кода внутренний профайлер отслеживает часто вызываемые участки кода (так называемый «Hot Code») и собирает информацию о типах аргументов (Type Feedback).

Если функция регулярно вызывается с неизменными типами данных, оптимизирующий компилятор TurboFan компилирует этот байт-код напрямую в высокопроизводительный машинный код под архитектуру текущего процессора.

Ловушка деоптимизации (Deoptimization)

JavaScript динамически типизирован. Если TurboFan оптимизировал функцию для работы с числами (number), а затем в нее внезапно передается строка (string):

deoptimization.js
function add(a, b) {
  return a + b;
}

// TurboFan оптимизирует функцию для чисел (Inline Caching)
for (let i = 0; i < 10000; i++) {
  add(5, 10);
}

// Неожиданная смена типа данных:
add("5", "10"); // Срабатывает деоптимизация (Bailout)!

Движок сбрасывает машинный код (совершает bailout) и возвращается к медленной интерпретации байт-кода. Именно поэтому написание мономорфного кода со стабильными структурами данных критично для высокой производительности.


2. Что такое Execution Context (Контекст выполнения)?

С момента запуска программы JavaScript оценивает все конструкции внутри изолированных сред — Execution Context (EC). Проще говоря, Execution Context — это обертка («коробка»), в которой содержится и выполняется текущий фрагмент кода.

Существует два основных типа:

  1. Global Execution Context (GEC): Создается автоматически при старте скрипта. Существует в единственном экземпляре и инициализирует глобальный объект (window в браузере, global в Node.js).
  2. Function Execution Context (FEC): Создается при каждом вызове функции.

Каждый контекст проходит через две обязательные фазы:

Фаза 1: Creation Phase (Фаза создания)

До выполнения первой строчки кода движок сканирует область видимости и резервирует память:

  • Связывается глобальный объект (window или global).
  • Определяется ссылка this.
  • Формируются структуры Lexical Environment и Variable Environment.
  • Резервируется память под переменные и функции (именно на этом уровне реализуется механизм Hoisting).

Фаза 2: Execution Phase (Фаза выполнения)

Движок последовательно проходит по коду сверху вниз, присваивает переменным реальные значения и запускает функции.


3. Всплытие (Hoisting) и временная мертвая зона (TDZ)

Всплытие не означает физическое перемещение кода наверх файла. Это прямое следствие того, что в Creation Phase движок выделяет слоты памяти под переменные до начала фактического исполнения.

Разница кроется в способе инициализации различных объявлений:

hoisting-tdz.js
console.log(a); // undefined
console.log(b); // ReferenceError: Cannot access 'b' before initialization

var a = 10;
let b = 20;
  • Ключевое слово var: Переменная регистрируется в памяти и сразу инициализируется значением undefined. Обращение к ней до строки объявления возвращает undefined без фатальной ошибки.
  • Ключевые слова let и const: Память также резервируется, но переменная остается неинициализированной. Промежуток между входом в область видимости и фактической строкой инициализации называется Temporal Dead Zone (TDZ). Обращение к переменной внутри TDZ генерирует ошибку ReferenceError.

4. Стек вызовов и архитектура памяти: Stack vs Heap

JavaScript является однопоточным языком с единственным стеком вызовов. Порядок выполнения контролируется через Call Stack, а хранение сложных данных координируется в куче — Memory Heap.

Характеристика Стек (Call Stack) Куча (Memory Heap)
Назначение Поток исполнения, примитивы и адреса-указатели (pointers) Сложные объекты, массивы, функции
Структура Строгий порядок LIFO (Last In, First Out) Динамическая, неструктурированная область памяти
Скорость доступа Максимально быстрый прямой доступ Чуть медленнее из-за разыменования указателей
memory-allocation.js
const id = 101;                                 // Хранится прямо в стеке
const user = { name: "Elgun", role: "Dev" }; // Объект в куче, адресная ссылка — в стеке

В стеке находится только указатель с адресом объекта в куче (0x0012FA). При присваивании объекта другой переменной копируется именно этот указатель, а не сам объект:

reference-mutation.js
const userA = { name: "Ali" };
const userB = userA; 

userB.name = "Murad";
console.log(userA.name); // "Murad" (Оба указателя ссылаются на один и тот же адрес в куче)

Стек вызовов и переполнение (Stack Overflow)

Каждый вызов функции помещает новый кадр (Stack Frame) в Call Stack. По завершении выполнения кадр выталкивается (pop). Бесконечная рекурсия без базового условия исчерпывает память стека, вызывая ошибку Maximum call stack size exceeded.


5. Сборка мусора: Алгоритм Mark-and-Sweep

Память в куче не может разрастаться бесконечно. Встроенный сборщик мусора (Garbage Collector, GC) периодически выявляет и освобождает неиспользуемые ячейки памяти.

Современные движки используют алгоритм Mark-and-Sweep («Пометить и смести»):

  1. Корни (Roots): Базовым набором корней выступают глобальные переменные и локальные переменные в активных кадрах стека.
  2. Mark (Маркировка): GC обходит дерево ссылок от корней, помечая все найденные объекты как «достижимые» (reachable), а затем рекурсивно проверяет их дочерние ссылки.
  3. Sweep (Очистка): Все объекты в куче, до которых нельзя добраться по цепочке ссылок от корней, признаются недостижимыми, и выделенная под них память освобождается.

Опасность утечек памяти (Memory Leaks)

Если данные больше не нужны логике приложения, но на них случайно осталась ссылка — например, в забытом обработчике событий (addEventListener), глобальной переменной или висячем замыкании, — сборщик мусора не сможет их удалить. Это приводит к постепенному росту потребления RAM и падению вкладки браузера.


Итоги

  • Движок V8 не просто интерпретирует код; он транслирует AST → Байт-код (Ignition) → Оптимизированный машинный код (TurboFan).
  • Контекст выполнения (Execution Context) управляет кодом в рамках фаз создания и выполнения. Hoisting и TDZ — прямое следствие архитектуры фазы создания.
  • Стек и Куча: Примитивы и ссылки хранятся в быстром стеке вызовов, а сложные объекты — в динамической куче памяти.
  • Сборщик мусора опирается на принцип достижимости: потерянные ссылки освобождают ресурсы системы.

Освоив базовую механику движка, следующим логичным шагом становится изучение того, как эти структуры взаимодействуют через Scope Chain, Lexical Environment и Closures (замыкания).

Поделиться статьей

JavaScript под капотом: Движок V8, Контекст выполнения (Execution Context) и Архитектура памяти