闭包和ES2015

3
我很抱歉又要问一个闭包问题,但我想澄清一下我对JavaScript中闭包实现方式的理解。
考虑以下代码:
01 'use strict';
02 function foo() {}
03 foo();

我在今年早些时候的一个问题中已经确定,这里概念上(如果不是由于引擎优化而实际上)创建了一个闭包。

直到第3行调用foo时,才会创建相应的执行上下文。

因此,据我所知,根据规范评估此代码时:

  1. 每个执行上下文都有一个“词法环境”组件,用于解析其中的代码所做的标识符引用(8.3,表23)。
  2. 调用FunctionCreate,并传递对当前执行上下文的“词法环境”组件的引用(称为“作用域”)(14.1.19)。
  3. FunctionCreate调用FunctionInitialize,并传递“scope”(9.2.5)
  4. FunctionInitialize确保正在创建的函数对象的 [Environment] 内部插槽设置为“scope”的值(指向当前执行上下文的“词法环境”组件的引用)(9.2.4)

最后,当实际调用 foo 时,我发现规范更难以解释。

  1. PrepareForOrdinaryCall 中,调用的新执行上下文的 "词法环境" 被设置为调用 NewFunctionEnvironment (9.2.1.1) 的结果。
  2. NewFunctionEnvironment 将对外部执行上下文的 "词法环境" 组件(函数对象的 [[Environment]] 插槽)的引用复制到正在构建的执行上下文的 "词法环境" 组件的 EnvironmentRecord 中作为 "外部词法环境引用" (8.1.2.4)。

因此,闭包是以两个步骤实现的:

  1. 在函数对象实例化时,会创建一个函数对象和封闭执行上下文的"词法环境"之间的链接。这是函数对象的[[Environment]]内部插槽。
  2. 当调用函数时,对外部执行上下文的封闭"词法环境"组件的引用(函数对象的[[Environment]]插槽的内容)会被复制到新执行上下文的"词法环境"组件的规范-不精确定义(?)/EnvironmentRecord(?)子组件中。

这样说是否准确?


1
@zeroflagL,我引导您前往上述链接的问题。https://dev59.com/eojca4cB1Zd3GeqPrheY - Ben Aston
好的,但是那个全局对象应该是存在的吧? - Alnitak
@Alnitak - 全局执行上下文的“词法环境”组件应该是存在的。我不确定它是否会有任何东西在里面,因为全局变量实际上是属性,这一点我在评论中提到了。 - Ben Aston
首先,规范非常明确地说明了什么可以作为闭包的资格,而您的示例不是其中之一。闭包需要在另一个函数内创建或者是箭头函数。其次,您所提到的是抽象操作。引擎需要表现得“好像”,仅此而已。这并不是真实的,也没有特定的实现被暗示或要求。这只是确保符合规范的引擎之间行为一致的一种方式。 - a better oliver
1
FYI,全局环境和普通函数环境之间的主要区别在于全局环境是由对象支持而不是一些内部表。也就是说,像你所说的那样,全局变量成为全局对象的属性。但是,它仍然像任何其他环境一样运作。它并不特殊,只是在环境链的末尾。全局变量并不是神奇地可访问的。另外,当在全局范围内定义letconstclass时,它们不会成为全局对象的属性。因此,是的,每个函数都会关闭全局环境。 - Felix Kling
显示剩余10条评论
1个回答

1

这听起来差不多吧?

基本上是的。我只是不会使用“复制”这个词,而是使用“链接”。为了简化一下:

  1. 当一个函数被创建时,它会存储对其创建环境的引用。
  2. 当函数被执行时,这个存储的环境成为新创建的函数环境的“外部环境”。

或者用图片表示:

                  +----------------+                     +----------------+
   Function       |                |   [[Environment]]   |     Outer      |
   creation       |    Function    |-------------------->|  Environment   |
                  |                |                     |                |
                  +----------------+                     +----------------+
                                                                  ^        
                                                                  |        
                                                                  |        
                  +----------------+                              |        
   Function       |    Function    |  outer environment reference |        
   execution      |  Environment   |------------------------------+        
                  |                |                                       
                  +----------------+                                       

这种情况发生在每个函数中,根据你对闭包的定义1,这使得每个函数都成为一个闭包(或者不是)。

1:我认为对于函数作为闭包,有以下两种看法:

  • 如果一个函数存储了它创建时所在环境的引用,那么它就是一个闭包(适用于JavaScript中的所有函数)。
  • 如果一个函数存储了它创建时所在环境的引用并且“离开”了该环境(即该环境“停止存在”),那么它就是一个闭包。当然,并不总是这种情况。

谢谢。我说规范对当前函数调用的执行上下文的"LexicalEnvironment"中的"EnvironmentRecord"的命名(以及位置)是不精确的,这样说是否正确?我所指的是将链接到函数对象的[[Environment]]插槽目标放置在其中的内存位置。 - Ben Aston
我不会说它是不精确的。规范只是定义API和行为。如何实现这个API和行为,是一个实现细节。此外,请注意规范中说:“词法环境是一种规范类型”,网址为http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-specification-types。 - Felix Kling
因此,执行上下文看起来像(伪代码):{ LexicalEnvironment: { OuterEnvironmentReference: <link to LexicalEnvironment of enclosing execution context>, ... }, ... }。如果是这样,请问您能否确认该属性的名称(在这里,“OuterEnvironmentReference”由规范留给实现)? - Ben Aston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接