有人能解释一下这个 JavaScript 自执行函数吗?

12
var foo = (function(){
  var x = 0;
  return function(){return x++;};
})()

对于这个问题,我最大的误解是为什么 var x = 0 表达式只运行一次。


我认为他想了解有关JavaScript闭包的内容。 - pastjean
@pastjean,是的,我实际上在我的回答中提到了那个。 :P - Jacob Relkin
6个回答

16

你的代码:

var foo = (function(){
  var x = 0;
  return function(){return x++;};
})()

等同于这段代码:

function f(){
  var x = 0;
  return function(){return x++;};
}
var foo = f();
很容易看出来,当你像这样分解它时,函数f()仅被调用一次。它定义了x,然后返回一个在 f本地作用域内定义的函数。这个新函数通常被称为“匿名函数”(意思是它没有名称)或“闭包”。事实上,所有 javascript 中的函数都是“闭包”——无论它们是否具有名称。术语“闭包”只是指该函数保留对其父函数作用域中定义的变量的访问权——即使父函数已经退出。
现在,foo 包含从f返回的新函数(闭包)。您可以随意调用 foo() ——每次调用时,将返回并且后增加x的值。由于x存在于闭包的父作用域中,所以其值将在对闭包的多次调用之间保持不变。
此外...一旦f()退出,其他代码就无法再访问 x了——这基本上意味着x现在是闭包的“私有数据”。相当神奇,对吧?

所有的JS函数都是闭包吗?function(x){return x;}是一个身份函数,而不是闭包——它不需要词法状态。 - tobyodavies
1
@tobyodavies:但即使是“身份函数”仍然保留对其父范围的引用——它不使用来自父范围的任何内容并不意味着该函数在某种程度上是不同类型的实体。在其他一些语言中,函数和闭包是不同的语言级别构造。在JavaScript中,所有函数都是闭包。 - Lee
闭包是一个具有自由变量的一等函数,这些变量在词法环境中绑定。实现与任何语言级别问题无关,因为您可以拥有相同语言的不同实现。闭包定义的重要部分是函数具有自由变量,即“使用”其词法环境。我相当确定,在ECMAScript标准中没有要求实现在函数不需要时保留指向词法作用域的指针。 - tobyodavies
2
@tobyodavies:任何仍然支持eval()的ECMA版本将要求所有函数保留对父作用域的引用。可能还有其他例子,但这是显而易见的一个。jsFiddle上的证明 - Lee
公平的观点,没意识到JS的eval使用调用者的词法作用域...取消1ed。 - tobyodavies

6
变量 foo 赋值为自执行函数的结果,该函数如下:
声明一个名为 x 的变量,并将其初始化为 0。 返回一个函数,当调用它时,将增加 x 的值。
因此,此时,foo 引用了一个函数
你可以这样调用它:
foo();

第一次调用时,返回的值将为0,然后是12等。
嗯,等一下...难道不应该是123吗?你已经走在了正确的轨道上,但在这种情况下,这个结论不成立的原因是因为前增量后增量之间的区别(++var vs var++)。它们的区别在于前增量的结果是变量增加后的值,而后增量的结果是变量增加前的值。
这个例子阐述了闭包的概念,它意味着内部函数可以访问其周围函数定义的变量。

2
稍作更正,应该是 0,递增的 结果1,但返回值是原始值 0。如果是 ++x,则会得到 12 等。 - Nick Craver

3

让我们分解一下...首先,我们定义一个匿名函数:

(function() { ... })

我们随即立即执行它:
(function() { ... })()

这个执行的结果是另一个函数:
function(){return x++;}

当我们创建上述函数时,闭包捕获了x=0。然后我们将这个结果函数分配给foo:

var foo = function(){return x++;}

闭包捕获了变量x的值。每当执行foo时,x都会增加。


1

立即调用的匿名函数,后面跟随()(不传递参数)。当执行该函数时,它会返回另一个函数,该函数有自己的x变量,运行时会递增。

因此,由于创建它的x继续增加,所以foo()第一次运行将是0,第二次运行将是1等。


0

实际上,在这种情况下,x是分配给foo对象的块/闭包中的局部变量。每次调用foo()时都会增加一个。

http://jsfiddle.net/3X283/中查看它的运行情况。


-1 这不是一个对象,而是一个闭包。它们相关但不同。这并没有回答问题。 - tobyodavies

0

这就是所谓的闭包,其中定义了两个函数 - 内部函数(即闭包)和外部函数,后者创建并返回闭包。

您的代码立即调用外部函数,并将结果(闭包)分配给foo

请注意,闭包内部的代码不包括语句var x = 0;,因此当您调用foo()时,它仅执行闭包内部的代码(return x++;)。

这里引用的x是在调用中实例化的。闭包有趣的地方在于,这个x在外部函数的调用之间是不同的 - 请考虑下面的示例。foo和bar将独立增加,因为它们引用不同的x

function makeClosure(){
  var x = 0;
  return function(){return x++;};
}

var foo = makeClosure();
var bar = makeClosure();

foo(); //returns 0
foo(); //returns 1
bar(); //returns 0
foo(); //returns 2

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