根据维基百科的定义,正如问题中提到的,闭包是
是一个函数或对函数的引用以及引用环境——一个存储对该函数的每个非局部变量(也称为自由变量)的引用的表。
对如何维护执行上下文和词法环境的理解是已知的,这里的目标是理解函数何时返回,how该引用环境是否得到维护/引用?
让我们开始。
在部分8.6.2在 ECMA 262 v 5 规范中,列出了internalECMAScript 对象的属性。这里需要指出的是表 9 中的 [[Scope]] 属性。根据该属性的描述,它被描述为
定义 Function 对象执行环境的词法环境。在标准内置 ECMAScript 对象中,只有 Function 对象实现了 [[Scope]]。
正如我们将看到的,函数对象的 [[Scope]] 属性将始终设置为父级的词法环境。我们在章节中看到这一点13.2其中讲的是创建函数对象的过程。 (请注意:此上下文中的函数对象指的是本机 ECMAScript 对象,而不是通过代码可访问的函数对象)。
创建函数时,它将内部 [[Scope]] 属性设置为正在运行的执行上下文的 VariableEnvironment、LexicalEnvironment 或全局环境,具体取决于该函数是否为函数声明, 函数表达式或通过创建函数构造函数.
当控制权交给全局代码时,以及当控制权进入函数代码时,声明绑定实例化将作为初始化执行上下文的一部分发生。声明绑定实例化的一部分是通过创建 13.2 节中提到的函数对象来将函数声明绑定在当前上下文的范围内。下面的例子展示了这一点:
例如
// The global execution context has already been initialized at this stage.
// Declaration binding instantiation has occurred and the function
// foo is created as a new native object with a [[Scope]] property
// having the value of the global execution context's VariableEnvironment
function foo() {
// When foo is invoked, a new execution context will be created for
// this function scope. When declaration binding instantiation occurs,
// bar will be created as a new native object with a [[Scope]] property
// having the value of the foo execution context's VariableEnvironment
function bar() {
}
bar(); // Call bar
}
foo();
另一件需要注意的事情是进入/创建执行上下文时发生的过程当进入一个函数时。以下是所发生情况的摘要。
- 通过内部调用 NewDeclarativeEnvironment 创建新的词法环境类型。函数的 [[Scope]] 属性将被设置为outer参考以便维护“词法环境”链。 (请记住, [[Scope]] 属性已设置,并且将始终是父级的词法范围。此外,词法环境链是我编写的一个短语,该概念指的是通过外部引用遍历词法环境直到标识符来解析标识符可以解决。)
- 将 LexicalEnvironment 和 VariableEnvironment 设置为步骤 1 中新创建的词法环境。
- 执行声明绑定实例化。
了解函数通过内部 [[Scope]] 属性维护对其父词法环境的引用后,我们现在可以了解闭包是如何工作的。
<script>
// foo.[[Scope]] was set to the global environment during the global execution context initialization
function foo() {
var x = 1;
// bar.[[Scope]] was set to foo's lexical environment during foo's execution context initialization
function bar() {
var y = 2;
alert(x + y);
}
return bar;
}
var dummy = foo(); // Assign variable binding "dummy" to a reference of the "bar" function.
dummy(); // Calls the "bar" function code. The reference still has it's [[Scope]] property set, thus the identifier look-ups work as expected when resolving the identifiers.
alert(dummy.name); // Alerts "bar";
</script>
因此,为了回答这个问题,函数父 LexicalEnvironment 通过函数的内部 [[Scope]] 属性进行持久化。请注意,函数内的局部变量可以在函数运行时解析,只有“自由变量”需要跟踪,并通过 [[Scope]] 属性来完成。
Note:如果我的信息不正确,请在下面评论。