![ECMAScript](https://lh3.googleusercontent.com/-eWS_tWrLYDw/VukDXYnAXgI/AAAAAAAAAWU/kqLdK1PHJH4uQdckngCUpaexiIWp-cweA/s0/ECMAScript.png)
Про устройство JavaScript написано много статей. В первую очередь, это "JavaScript. Ядро." Дмитрия Сошникова, перевод статьи Ричарда Корнфорда и поста Дмитрия Франка. Но для того чтобы хорошо разобраться в какой-либо технологии лучше обратиться к первоисточникам. В данном случае к стандарту ECMA-262 ECMAScript Language Specification. Я рассматриваю этот пост как облегченный способ начать изучение стандарта. Рекомендую переходить по ссылкам, вчитываться в текст спецификации и составлять собственные схемы.
Как работают замыкания в JavaScript
Основные структуры ECMAScript это execution context, lexical environment и environment record. Они связаны следующим образом:
![](https://habrastorage.org/files/ce8/a16/718/ce8a167188b9417c8499112819db4489.png)
Кроме VariableEnvironment в execution context есть еще LexicalEnvironment, подробнее про различия можно прочитать в ECMAScript 5 spec: LexicalEnvironment versus VariableEnvironment.
ThisBinding будет рассмотрен в следующей части статьи
В environment record хранятся значения переменных. Если объявить
var a = 1
, то в текущем record появится {a: 1}. В lexical environment кроме record есть еще поле outer. Outer ссылается на внешний lexical environment, например, в случае вложенных функций.Поиск переменных начинается с VariableEnvironment текущего контекста. Если переменная с таким именем не найдена в record, то она ищется в outer environment по цепочке.
При запуске программы создаются глобальные context и environment. В качестве record используется global object.
![](https://habrastorage.org/files/2f2/397/941/2f239794152740abbb497b5ca7f7e839.png)
Когда интерпретатор встречает ключевое слово function он создает FunctionObject. В свойство scope созданного FunctionObject записывается ссылка на текущий lexical environment.
Запись
function f(){}
практически эквивалентна записи var f = function(){}
за исключением того, что в первом случае FunctionObject будет создан при входе в блок, содержащий function f()
, а во втором при выполнении конкретной строчки.![](https://habrastorage.org/files/b5e/a79/0e5/b5ea790e571a45158134aa5cbd8b2514.png)
При каждом вызове функции создаются новые context, environment и record. Контекст кладется в стек, при выходе из функции он уничтожается. В outer созданного environment записывается scope вызываемого FunctionObject. Если функция была объявлена в глобальном контексте, то outer будет указывать на global environment.
![](https://habrastorage.org/files/8ff/63e/a7c/8ff63ea7c9b948eeb96e017d3d1c802e.png)
Теперь рассмотрим замыкание, когда одна функция возвращает другую.
var x = 1;
function f() {
var x = 2;
function g() {
return x;
}
return g;
}
f()();
При вызове функции f создается context и environment для f, а также FunctionObject для
function g
. В scope записывается ссылка на текущий environment из VariableEnvironment. При выходе из f контекст уничтожается, но environment останется, так как на него есть ссылка из возвращенного FunctionObject.![](https://habrastorage.org/files/c79/71c/d99/c7971cd99e394bd68b128a76819d12cd.png)
При вызове возвращенной из f функции g создается контекcт и environment для g. В outer нового environment записывается scope из вызываемого FunctionObject. Поиск переменной x начинается с текущего VariableEnvironment и затем продолжается по outer. В результате будет возвращено значение
x = 2
.![](https://habrastorage.org/files/6dc/393/024/6dc393024cb647e8b5f8a1acf000622d.png)
В следующей части статьи разберем как с точки зрения ECMAScript работает this.