8

我正在阅读有关JavaScript 闭包的内容。我熟悉Execution Contexts,如何维护Lexical Environment,并且非常熟悉Lexical Scoping

我想知道JavaScript 中的闭包是如何创建维护的。有时我很难在不知道它实际上是如何做的情况下掌握这些重要的概念。我知道,根据维基百科,闭包是

是一个函数或对函数的引用以及引用环境——一个存储对该函数的每个非局部变量(也称为自由变量)的引用的表。

但我的问题是,我想知道如何根据ECMA 规范创建和维护闭包。我不是在寻找闭包理论的高级解释,请在您的回答中参考 ECMA 规范。

注意:请不要认为这是重复的,除非答案解释了使用 ECMA 规范的关闭。同样,我对有人引用维基百科并举个例子不感兴趣,我想完全理解 JavaScript 是如何做到这一点的。(我对 SO 的这个问题很熟悉)。

4

1 回答 1

7

根据维基百科的定义,如问题中所述,闭包是

是一个函数或对函数的引用以及引用环境——一个存储对该函数的每个非局部变量(也称为自由变量)的引用的表。

对如何维护执行上下文和词法环境的理解是已知的,这里的目标是了解何时返回函数,该引用环境是如何维护/引用的?

让我们开始。

在 ECMA 262 v 5 规范的第8.6.2节中,它列出了ECMAScript 对象的内部属性。这里要指出的是表 9 中的 [[Scope]] 属性。根据该属性的描述,它被描述为

定义执行 Function 对象的环境的词法环境。在标准的内置 ECMAScript 对象中,只有 Function 对象实现 [[Scope]]。

正如我们将看到的,函数对象的 [[Scope]] 属性将始终设置为父级的词法环境。我们在13.2节中提到了这一点,该节讨论了创建函数对象的过程。(请注意:此上下文中的函数对象是指本机 ECMAScript 对象,而不是通过代码可访问的函数对象)。

创建函数时,它会将内部 [[Scope]] 属性设置为正在运行的执行上下文的 VariableEnvironment、LexicalEnvironment 或 Global Environment,具体取决于函数是函数声明函数表达式还是通过Function 构造函数创建的。

当控制权交给全局代码时,以及当控制权进入函数代码时,声明绑定实例化作为初始化执行上下文的一部分发生。声明绑定实例化的一部分是通过创建 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();

另一件事是在进入函数时进入/创建执行上下文时发生的过程。以下是所发生情况的摘要。

  1. 通过内部调用 NewDeclarativeEnvironment 创建一个新的词法环境类型。函数的 [[Scope]] 属性将设置为外部引用,以便维护“词法环境”链。(请记住,已设置 [[Scope]] 属性并将始终是父级的词法范围。此外,词法环境链是我编造的一个短语,该概念指的是通过通过外部引用遍历词法环境直到标识符来解析标识符可以解决。)
  2. 将 LexicalEnvironment 和 VariableEnvironment 设置为步骤 1 中新创建的 Lexical Environment。
  3. 执行声明绑定实例化。

知道函数通过内部 [[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]] 属性完成。

注意: 如果我的信息不正确,请在下方评论。

于 2013-02-27T18:33:29.570 回答