JavaScript闭包 - 使用ECMA规范,请解释如何创建和维护闭包

con*_*att 5 javascript closures ecma262 ecmascript-5

我正在阅读有关JavaScript闭包的内容.我熟悉执行上下文,如何维护词汇环境,以及非常熟悉词法范围.

我想知道如何创建维护 JavaScript中的闭包.有时我很难掌握这些重要的概念而不知道它是如何实现的.我知道,根据维基百科的说法,关闭是

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

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

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

con*_*att 6

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

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

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

让我们开始.

在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();
Run Code Online (Sandbox Code Playgroud)

另一个需要注意的是在输入函数时输入/创建执行上下文发生的过程.以下是所发生情况的摘要.

  1. 通过内部调用NewDeclarativeEnvironment创建一个新的词法环境类型.函数的[[Scope]]属性将被设置为外部引用,以便维护"Lexical Environment"链.(请记住,[[Scope]]属性已设置并且始终是父级的词法范围.此外,Lexical Environment chain是我编写的一个短语,该概念指的是通过外部引用遍历Lexical Environments来解析标识符,直到标识符为止.可以解决.)
  2. 在步骤1中将LexicalEnvironment和VariableEnvironment设置为新创建的词法环境.
  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>
Run Code Online (Sandbox Code Playgroud)

因此,为了回答这个问题,函数父LexicalEnvironment通过函数的内部[[Scope]]属性保持不变.注意,在运行函数时,可以解析函数中的局部变量,只需要跟踪"自由变量",并通过[[Scope]]属性完成.

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

  • 似乎是对的.几年前,我在Cornford的http://jibbering.com/faq/notes/closures/和(当时的ES3)规范的帮助下,对ES的封闭机制有类似的顿悟理解.该文章基于ES3,但大部分仍然适用.如果你还没读过,我强烈推荐它. (2认同)
  • 由于OP的问题是"它是如何工作的?",重要的是要记住规范对诸如`[[Scope]]`属性和`LexicalEnvironment`对象之类的术语的使用仅用于描述目的,并不一定规定了如何在真正的JavaScript解释器中实现函数闭包. (2认同)