JS闭包和执行上下文的创建

时间:2014-05-11 17:23:47

标签: javascript closures

你可以在下面澄清我的误解吗?

如果JS中函数的执行上下文是在调用/调用函数时创建的,而不是在声明函数时创建的,那么在下面的代码中,内部是一个闭包吗?如果是这样,为什么?内部尚未被调用,并且由于执行上下文是在调用时创建的,因此内部无法存储对i的引用。

function foo(i) {
   function inner() {
       return i;
   }
   return inner;
}
var f = foo(3);

内部什么时候引用foo的执行上下文?何时调用或定义它?在上面的代码中,我还没有调用内部。

另外,如果你解释JS在看到函数定义时所做的事情(与函数调用/调用相反),我将不胜感激

谢谢,

4 个答案:

答案 0 :(得分:1)

请参阅有关其工作原理的评论。

function foo(i) {
   function inner() { // closure or a new scope is created
       return i;  // but parent scope's variable can be used in child scope
   }
   return inner; // returning reference to function
}
var f = foo(3); // f contains the function reference
f(); // 3, since i is in the parent scope, it is retained.

由于i是一个形式参数,因此它可以在整个函数中访问,因为它位于最顶层,没有任何闭包问题。

javascript中的范围是以功能为中心的。因此,只要函数执行,它就会创建一个闭包或范围。

答案 1 :(得分:1)

在我看来,这里有一个术语问题可以解决相互矛盾的问题。有一种称为闭包的设计概念,人们可能会说是允许有闭包的代码设计。这显然会在代码声明中出现。这是一个设计概念,但由于尚未执行任何代码,因此没有包含变量值,函数引用等的实际对象......

还有一个实际的函数对象在其父函数执行之后持续存在,这使得闭包实际工作。那个"关闭对象"是在执行时创建的,实际上,每次执行函数时都会创建一个新对象。

您的案例中的实际闭包对象是在执行foo()时创建的。事实上,每次执行它(并将返回值保存到变量中)时,都会创建一个新的闭包。代码声明为闭包创建了机会,但在执行函数foo之前,不会创建实际的闭包。

以下是此代码中发生的情况:

function foo(i) {
   function inner() {
       return i;
   }
   return inner;
}

var f = foo(3);
var result = f();

首先声明foo()函数。声明它返回对inner()函数的引用。这为闭包设置了机会,但尚未创建闭包。

然后,当您执行f = foo(3)时,执行函数foo。这将创建一个函数范围对象(在解释器中)。此函数作用域对象中的参数i3的此特定执行中具有值foo,因此在此作用域对象中。在foo执行时,它会返回对inner的引用,然后将其分配给变量f。因此,变量f包含对inner()的引用。

通常,当一个函数完成执行时,它的函数作用域对象被垃圾收集(例如释放),但在这种情况下inner仍然有对该作用域对象的引用,并且f包含对inner的调用的引用。因此,该范围对象不符合垃圾收集条件并保持活动状态。这称为闭包。因为存在的东西引用了函数范围内的某些东西,所以它不能像通常那样被垃圾收集。

定义函数的行为为闭包创建了机会,但每次运行foo()时都会在执行时创建实际闭包。

实际上,如果你执行了f = foo(3),那么你创建了一个闭包,如果执行g = foo(4),你就会创建另一个闭包。每次执行foo并将返回值保存在变量中时,都会创建一个闭包。请记住,这种类型的闭包实际上只是一个函数作用域对象,它不能立即被垃圾收集,因为某些代码在函数本身的生命周期之后保留了对该函数作用域中对象的引用。

就我个人而言,当我真正从垃圾收集器的角度考虑它们时,闭包更容易理解。它们只是函数作用域对象(所有函数都有),它们仍然可以被垃圾收集,因为某些代码保留了对它们内部内容的引用。而且,创建这种类型的闭包的最常见方式是在外部代码中为父函数内部的本地函数提供引用。

答案 2 :(得分:0)

当JS创建一个函数时,它将其scope属性设置为等于当前环境:http://ecma-international.org/ecma-262/5.1/#sec-13.2

当JS执行一个函数时,它会创建一个新环境并将其“outer”(= parent)指针设置为函数的Scope:http://ecma-international.org/ecma-262/5.1/#sec-10.4.3

所以问题的答案是:闭包是在定义函数时创建的,而不是在调用时创建的。为了澄清,每次程序到达“函数”行时都会创建函数对象。如果两次调用外部函数,将会有两个不同的函数对象(有两个不同的[[scopes]])。

答案 3 :(得分:0)

Closure是一对a)函数的代码和b)调用帧链。 调用框架本质上是保存局部变量和参数的结构。

因此,当您返回inner函数时,您将返回以下内容:

struct Function {
  var functionBody;     // compiled function body
  var outerScopeChain = { i:3,
                          nextScope: null };
};

JS中的每个函数对象实际上都是一个闭包,outerScopeChain对于全局函数是空的。