1. Execution Context и интерпретатор JavaScript
Execution Context и интерпретатор JavaScript
Почему один и тот же код ведёт себя по-разному в зависимости от того, где он написан? Почему переменная, объявленная внутри функции, недоступна снаружи, а глобальная переменная вдруг оказывается undefined вместо ожидаемого значения? Всё это — следствие того, как JavaScript-движок создаёт и управляет контекстами выполнения (execution contexts). Понимание этого механизма — фундамент, без которого невозможно объяснить ни замыкания, ни this, ни асинхронность.
Как движок читает ваш код
JavaScript-движок (V8 в Chrome/Node.js, SpiderMonkey в Firefox) не выполняет код построчно с первой строки. Перед запуском он проходит две фазы: фазу компиляции (compilation phase) и фазу выполнения (execution phase).
В фазе компиляции движок сканирует весь код, находит объявления переменных и функций, выделяет под них память и создаёт структуры данных — лексическое окружение (lexical environment) и запись окружения (environment record). Именно здесь происходит то, что называют hoisting — подъём объявлений. Но об этом подробнее в следующей статье.
В фазе выполнения движок идёт по коду уже последовательно, присваивает значения, вызывает функции, вычисляет выражения.
Что такое Execution Context
Execution Context — это абстрактная среда, в которой выполняется JavaScript-код. Думайте о нём как о «пузыре», внутри которого живёт конкретный кусок кода: у него есть свои переменные, своё значение this и ссылка на внешнее окружение.
Каждый раз, когда вызывается функция, движок создаёт новый execution context. Когда функция завершается — контекст уничтожается.
Существует три вида контекстов:
this здесь равен window, в Node.js — global.eval(), на практике почти не используется.Каждый контекст содержит три ключевых компонента:
var, и объявления функций.let и const, а также ссылку на внешнее лексическое окружение (outer).this для данного контекста.Call Stack: стек вызовов
Call Stack (стек вызовов) — это структура данных типа LIFO (Last In, First Out), которая отслеживает, какой контекст выполнения активен в данный момент. Движок всегда выполняет контекст, находящийся на вершине стека.
Вот что происходит пошагово:
main() — создаётся новый контекст, помещается поверх глобального.main вызывается greet('Alice') — ещё один контекст ложится на стек.greet возвращает строку — её контекст снимается со стека.console.log выполняется в контексте main.main завершается — её контекст снимается.Именно поэтому в стектрейсе ошибки вы видите цепочку вызовов снизу вверх — это буквально снимок call stack в момент исключения.
Stack Overflow — это не просто название сайта. Это реальная ошибка, которая возникает, когда стек переполняется из-за бесконечной рекурсии. Движок имеет лимит на глубину стека (в V8 — около 10 000–15 000 фреймов в зависимости от платформы).
!Схема Call Stack и Execution Context
Лексическое окружение и цепочка областей видимости
Каждый execution context имеет ссылку outer на лексическое окружение родительского контекста. Это и есть механизм scope chain (цепочки областей видимости).
Когда inner обращается к x, движок не находит её в собственном лексическом окружении, идёт по ссылке outer в окружение функции outer, не находит там, идёт дальше — в глобальное окружение, находит x = 10. Это статическое (лексическое) связывание — цепочка определяется в момент написания кода, а не в момент вызова.
Именно поэтому замыкания работают так, как работают: функция «запоминает» лексическое окружение, в котором была создана, и сохраняет ссылку на него даже после того, как родительский контекст уничтожен.
После завершения makeCounter её контекст снят со стека, но лексическое окружение с переменной count не уничтожено — на него держит ссылку возвращённая функция. Это и есть замыкание (closure).
Как создаётся Execution Context: детали
Создание контекста происходит в два этапа.
Этап создания (creation phase):
LexicalEnvironment и VariableEnvironment.var инициализируются значением undefined.let и const регистрируются, но остаются в Temporal Dead Zone (TDZ) — обращение к ним до строки объявления вызовет ReferenceError.this.Этап выполнения (execution phase):
Глобальный контекст и его особенности
В браузере глобальный контекст создаёт объект window и связывает с ним глобальные переменные, объявленные через var. Это одна из причин, почему var в глобальной области видимости — антипаттерн: вы загрязняете глобальный объект.
В модулях ES (ES Modules) ситуация другая: каждый модуль получает собственный лексический контекст, и переменные не попадают в window даже при использовании var. Это одна из ключевых причин перехода на модульную систему.
Практический кейс: отладка через понимание контекстов
На реальном проекте встречается такая ситуация: разработчик жалуется, что функция возвращает undefined вместо значения.
Причина — var поднимается на уровень функции (function scope), а не блока. В фазе создания контекста userData инициализируется как undefined. Если условие не выполняется, присвоение не происходит, и функция возвращает undefined. Замена var на let сделала бы ошибку явной: ReferenceError в строке return userData сразу указал бы на проблему.
Понимание того, что происходит в фазе создания контекста, позволяет читать такие баги как открытую книгу — без часов отладки.
Execution Context в асинхронном коде
Важный нюанс: когда колбэк из setTimeout или промис выполняется, для него создаётся новый execution context. Но его лексическое окружение (outer) по-прежнему указывает на то место, где функция была определена, а не где она была вызвана.
Колбэк выполняется через секунду, контекст setup давно уничтожен — но message доступна, потому что замыкание удерживает ссылку на лексическое окружение. Это фундаментальный механизм, на котором строится вся асинхронная работа с состоянием в JavaScript.
> Execution Context — это не просто теория для собеседований. Это модель, которая объясняет поведение кода в любой ситуации: от простого замыкания до сложного асинхронного потока данных.
Понимание execution context, call stack и лексического окружения — это то, что отличает разработчика, который «знает JavaScript», от того, кто «понимает JavaScript». На senior-собеседовании вас не спросят «что такое execution context» в лоб — вас попросят объяснить поведение конкретного кода, и именно эта модель даст вам ответ.