JavaScript под капотом: Движок V8, Контекст выполнения (Execution Context) и Архитектура памяти
От абстрактного синтаксического дерева (AST) до машинного кода: как устроен движок V8, почему возникает деоптимизация и как браузер распределяет память между стеком и кучей.
Elgun Mammadli
Front End Developer

Большинство разработчиков начинают знакомство с JavaScript с console.log("Hello World"), объявления переменных или написания простых функций. Однако переход на уровень Senior требует понимания процессов, скрытых за синтаксисом: что на самом деле представляет наш код для машины и как движок выполняет его на физическом оборудовании?
В этой статье мы подробно разберем внутреннюю архитектуру JavaScript: конвейер оптимизации движка V8, жизненный цикл контекста выполнения (Execution Context) и низкоуровневые принципы управления памятью.
1. Анатомия движка: От исходного кода до машинных инструкций
JavaScript давно перестал быть чисто интерпретируемым языком; современные среды используют JIT-компиляцию (Just-In-Time). Наиболее распространенным движком является V8 от Google (он используется в Chrome, Node.js и Deno).
Попадая в движок, код проходит через следующий многоступенчатый конвейер:
Парсер и AST (Абстрактное синтаксическое дерево)
Движок начинает с лексического анализа: исходный текст разбирается на элементарные единицы — токены. Затем синтаксический анализатор строит на их основе AST (Abstract Syntax Tree) — иерархическую древовидную структуру, отражающую грамматику программы.
Ignition (Интерпретатор)
Интерпретатор V8, Ignition, принимает AST и генерирует компактный, независимый от платформы байт-код. Этот код запускается мгновенно без задержек на сложную компиляцию, что обеспечивает быстрый старт приложения.
TurboFan и Профайлер (JIT-оптимизация)
Параллельно с выполнением байт-кода внутренний профайлер отслеживает часто вызываемые участки кода (так называемый «Hot Code») и собирает информацию о типах аргументов (Type Feedback).
Если функция регулярно вызывается с неизменными типами данных, оптимизирующий компилятор TurboFan компилирует этот байт-код напрямую в высокопроизводительный машинный код под архитектуру текущего процессора.
Ловушка деоптимизации (Deoptimization)
JavaScript динамически типизирован. Если TurboFan оптимизировал функцию для работы с числами (number), а затем в нее внезапно передается строка (string):
Движок сбрасывает машинный код (совершает bailout) и возвращается к медленной интерпретации байт-кода. Именно поэтому написание мономорфного кода со стабильными структурами данных критично для высокой производительности.
2. Что такое Execution Context (Контекст выполнения)?
С момента запуска программы JavaScript оценивает все конструкции внутри изолированных сред — Execution Context (EC). Проще говоря, Execution Context — это обертка («коробка»), в которой содержится и выполняется текущий фрагмент кода.
Существует два основных типа:
- Global Execution Context (GEC): Создается автоматически при старте скрипта. Существует в единственном экземпляре и инициализирует глобальный объект (
windowв браузере,globalв Node.js). - Function Execution Context (FEC): Создается при каждом вызове функции.
Каждый контекст проходит через две обязательные фазы:
Фаза 1: Creation Phase (Фаза создания)
До выполнения первой строчки кода движок сканирует область видимости и резервирует память:
- Связывается глобальный объект (
windowилиglobal). - Определяется ссылка
this. - Формируются структуры Lexical Environment и Variable Environment.
- Резервируется память под переменные и функции (именно на этом уровне реализуется механизм Hoisting).
Фаза 2: Execution Phase (Фаза выполнения)
Движок последовательно проходит по коду сверху вниз, присваивает переменным реальные значения и запускает функции.
3. Всплытие (Hoisting) и временная мертвая зона (TDZ)
Всплытие не означает физическое перемещение кода наверх файла. Это прямое следствие того, что в Creation Phase движок выделяет слоты памяти под переменные до начала фактического исполнения.
Разница кроется в способе инициализации различных объявлений:
- Ключевое слово
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) | Динамическая, неструктурированная область памяти |
| Скорость доступа | Максимально быстрый прямой доступ | Чуть медленнее из-за разыменования указателей |
В стеке находится только указатель с адресом объекта в куче (0x0012FA). При присваивании объекта другой переменной копируется именно этот указатель, а не сам объект:
Стек вызовов и переполнение (Stack Overflow)
Каждый вызов функции помещает новый кадр (Stack Frame) в Call Stack. По завершении выполнения кадр выталкивается (pop). Бесконечная рекурсия без базового условия исчерпывает память стека, вызывая ошибку Maximum call stack size exceeded.
5. Сборка мусора: Алгоритм Mark-and-Sweep
Память в куче не может разрастаться бесконечно. Встроенный сборщик мусора (Garbage Collector, GC) периодически выявляет и освобождает неиспользуемые ячейки памяти.
Современные движки используют алгоритм Mark-and-Sweep («Пометить и смести»):
- Корни (Roots): Базовым набором корней выступают глобальные переменные и локальные переменные в активных кадрах стека.
- Mark (Маркировка): GC обходит дерево ссылок от корней, помечая все найденные объекты как «достижимые» (reachable), а затем рекурсивно проверяет их дочерние ссылки.
- Sweep (Очистка): Все объекты в куче, до которых нельзя добраться по цепочке ссылок от корней, признаются недостижимыми, и выделенная под них память освобождается.
Опасность утечек памяти (Memory Leaks)
Если данные больше не нужны логике приложения, но на них случайно осталась ссылка — например, в забытом обработчике событий (addEventListener), глобальной переменной или висячем замыкании, — сборщик мусора не сможет их удалить. Это приводит к постепенному росту потребления RAM и падению вкладки браузера.
Поделиться статьей
JavaScript под капотом: Движок V8, Контекст выполнения (Execution Context) и Архитектура памяти