JavaScript 闭包 vs 匿名函数

581

我和我的一位朋友正在讨论JS中的闭包是什么以及它不是什么。我们只是想确保我们真正理解它。

让我们拿这个例子来说。我们有一个计数循环,希望延迟一段时间后将计数器变量打印在控制台上。因此,我们使用setTimeout闭包来捕获计数器变量的值,以确保它不会打印N次值为N的值。

没有闭包或与闭包相似的任何东西的错误解决方案如下:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

循环结束后,它将打印i的值10次,即10。

所以他的尝试是:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

按预期打印0到9。

我告诉他,他没有使用闭包来捕获i,但他坚持说他使用了。我通过将for循环体放在另一个setTimeout中(将他的匿名函数传递给setTimeout),再次打印10次10来证明他没有使用闭包。如果我将他的函数存储在一个var中,并在循环之后执行它,也会打印10次10。因此,我的论点是他实际上并没有真正“捕获”i的值,使他的版本不是一个闭包。

我的尝试是:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

我捕获了i(在闭包中命名为i2),然后现在我返回另一个函数并传递它。 在我的情况下,传递给setTimeout的函数真正捕获了i

现在谁在使用闭包,谁不是?

请注意,两个解决方案都会延迟地将0到9打印到控制台,因此它们解决了原始问题,但我们想要理解这两个解决方案中哪个使用闭包来完成这一点。


2
我们刚刚达成了一项协议:正确的那个人将获得与此问题相关的SO积分。 - brillout
1
@leemes - 你们两个都在使用闭包。你们都创建了两个函数 - 一个外部函数和一个内部函数;而且你们两个的内部函数都是闭包。你们所有的函数都是lambda表达式(匿名函数)。请查看我的答案获取详细信息。 - Aadit M Shah
1
@blesh - 我不知道什么是修改的闭包。我看到你的链接指向C#代码。JavaScript支持修改的闭包吗? - Aadit M Shah
1
修改闭包是一个有点不准确的名称。基本上,OP的第一个示例说明了一个修改过的闭包。这将是您具有一些自由变量,在某个内部方法访问之前被改变的情况。例如,从“for”循环中访问“i”,在第一个示例中在“setTimeout”内部访问。它会在任何超时回调执行之前被修改为10。 - Ben Lesh
显示剩余17条评论
12个回答

672

编者按:正如这篇文章所解释的那样,JavaScript中的所有函数都是闭包。但是我们只对从理论角度来看比较有趣的这一子集感兴趣。因此,除非另有说明,否则任何对“闭包”一词的引用都将指代这个函数子集。

闭包的简单解释:

  1. 拿一个函数,我们叫它F。
  2. 列出F的所有变量。
  3. 这些变量可以分为两种类型:
    1. 局部变量(绑定变量)
    2. 非局部变量(自由变量)
  4. 如果F没有自由变量,则它不能是闭包。
  5. 如果F有任何自由变量(这些变量在F的父范围内定义),则:
    1. F只能有一个父范围与一个自由变量绑定。
    2. 如果F从那个父范围之外被引用,则它成为该自由变量的闭包。
    3. 该自由变量称为闭包F的上值。

现在让我们使用这个来确定谁使用闭包,谁不使用(为了解释的目的,我已经给函数命名):

情况1:你朋友的程序

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

在上面的程序中,有两个函数:fg。让我们看看它们是否是闭包:
对于f
  1. 列出变量:
    1. i2 是一个本地变量。
    2. i 是一个自由变量。
    3. setTimeout 是一个自由变量。
    4. g 是一个本地变量。
    5. console 是一个自由变量。
  2. 找到每个自由变量绑定的父作用域:
    1. i 绑定到全局作用域。
    2. setTimeout 绑定到全局作用域。
    3. console 绑定到全局作用域。
  3. 函数引用在哪个作用域中?全局作用域
    1. 因此,i 不被f闭合
    2. 因此,setTimeout 不被f闭合
    3. 因此,console 不被f闭合
因此,函数 f 不是一个闭包。
对于 g
  1. 列出变量:
    1. console 是一个自由变量
    2. i2 是一个自由变量
  2. 找到每个自由变量所绑定的父级作用域:
    1. console 绑定到全局作用域中。
    2. i2 绑定到 f 的作用域中。
  3. 函数引用在哪个作用域中?setTimeout 的作用域
    1. 因此,console 不被 g 闭合
    2. 因此,i2g 闭合
因此,当从setTimeout内部引用时,函数g成为自由变量i2的闭包(该变量是g的上值)。 对你不利的情况:你的朋友正在使用闭包。内部函数是一个闭包。 情况2:您的程序
for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

在上述程序中有两个函数:fg。让我们看看它们是否为闭包:
对于f
  1. 列出变量:
    1. i2是一个局部变量。
    2. g是一个局部变量。
    3. console是一个自由变量。
  2. 找到每个自由变量所绑定的父级作用域:
    1. console绑定到全局作用域。
  3. 函数在哪个作用域引用全局作用域
    1. 因此f没有闭合console
因此函数f不是闭包。
对于g
  1. 列出变量:
    1. console 是一个自由变量。
    2. i2 是一个自由变量。
  2. 找到每个自由变量绑定的父级作用域:
    1. console 绑定到全局作用域。
    2. i2 绑定f的作用域。
  3. 函数引用在哪个作用域中?setTimeout的作用域
    1. 因此,console不会被g 闭包
    2. 因此,i2g 闭包

因此,当从setTimeout内部引用时,函数g是自由变量i2g的上值)的闭包。

你做得很好:你正在使用闭包。内部函数是一个闭包。

所以你和你的朋友都在使用闭包。停止争论。我希望我已经为你们澄清了闭包的概念以及如何识别它们。

编辑:为什么所有函数都是闭包的简单解释(感谢@Peter):

首先,让我们考虑以下程序(它是control):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

  1. 从上述定义中,我们知道lexicalScoperegularFunction都不是闭包。
  2. 当我们执行程序时,我们期望message被弹出,因为regularFunction不是闭包(即它可以访问其父作用域中的所有变量,包括message)。
  3. 当我们执行程序时,我们观察到确实弹出了message

接下来,让我们考虑以下程序(这是alternative):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. 根据上述定义,我们知道只有closureFunction是一个闭包。
  2. 当我们执行程序时,我们期望不会弹出message的提示框,因为closureFunction是一个闭包(即它只能访问在函数创建时的所有非局部变量(参见此答案),这不包括message)。
  3. 当我们执行程序时,我们观察到实际上message被弹出了提示框。

我们从中可以推断什么?

  1. JavaScript 解释器对待闭包和其他函数的方式没有区别。
  2. 每个函数都会带着它的作用域链。闭包没有一个独立的引用环境。
  3. 闭包就像其他函数一样。只有当它们在一个外部作用域中被引用时,我们才称它们为闭包,因为这是一个有趣的情况。

42
接受了因为你详细地解释了正在发生的事情。最后,我现在更好地理解了闭包,或者更确切地说:变量绑定在JS中是如何工作的。 - leemes
3
在Case 1中,你说 gsetTimeout 的作用域内运行,但在Case 2中,你说 f 在全局作用域内运行。它们都在 setTimeout 中,那么区别是什么呢? - rosscj2533
11
请问这个观点的来源是什么?我从来没有看过一个定义能够说一个函数在一个作用域中可以成为闭包,而在另一个作用域中就不能。因此,这个定义似乎是我所熟悉的更一般定义的子集(请参见 kev's answer)。在那个定义中,无论在哪个作用域中调用,甚至从未被调用,一个闭包都是一个闭包。 - Briguy37
13
我同意你对于闭包的看法,但你好像在说JavaScript中的普通函数闭包之间有所不同。实际上它们并没有区别;在内部,每个函数都会携带一个对特定作用域链的引用。JavaScript引擎并不认为它是一个不同的情况。没有必要进行复杂的检查清单;只需知道每个函数对象都携带着词法作用域。变量/属性可以全局访问并不会使函数成为非闭包(这只是一个无用的情况)。 - Peter
15
@Peter- 你说得对,一个普通函数和闭包之间没有区别。我进行了一项测试来证明这一点,结果证明了你的观点:这是控制,这是替代方案。你所说的确有道理。JavaScript解释器需要为闭包进行特殊的簿记。它们只是具有头等函数的词法作用域语言的副产品。我的知识仅限于我读到的东西(是错误的)。谢谢你纠正我。我会更新我的答案以反映相同的内容。 - Aadit M Shah
显示剩余35条评论

98
根据 闭包 的定义:

"闭包"是一个表达式(通常是函数),它可以具有自由变量和绑定这些变量的环境("关闭"表达式)。

如果你定义了一个使用在函数外部定义的变量的函数,那么你正在使用闭包(我们称该变量为自由变量)。他们都使用闭包(即使在第一个示例中也是如此)。

1
第三个版本如何使用在函数外定义的变量? - Jon
1
@Jon 返回的函数使用了在外部定义的 i2 - kev
1
@kev 如果你定义一个使用在函数外部已经定义的变量的函数,那么你就正在使用闭包……然后在“Aadit M Shah”的“案例1:你朋友的程序”答案中,“函数f”是闭包吗?它使用了在函数外部定义的变量 i。全局范围是否引用了某个限定词? - internals-in

55

简而言之,Javascript闭包允许函数访问在词法父函数中声明的变量

为了理解闭包,重要的是要了解JavaScript如何作用域变量。

作用域

在JavaScript中,通过函数定义作用域。每个函数定义一个新的作用域。

考虑以下示例:

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

调用 f 输出

hello
hello
2
Am I Accessible?

现在考虑这样一种情况,我们有一个在另一个函数f中定义的函数g

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f
我们将称fg词法父级。 如前所述,我们现在有两个作用域;作用域f和作用域g
但是一个作用域“包含”另一个作用域,那么子函数的作用域是否属于父函数的作用域?在父函数的作用域中声明的变量会发生什么?我能否从子函数的作用域中访问它们? 这正是闭包介入的地方。 闭包 在JavaScript中,函数g不仅可以访问在作用域g中声明的任何变量,还可以访问在父函数f的作用域中声明的任何变量。
考虑以下内容;
function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

调用 f 会输出

hello
undefined

让我们看看代码行console.log(foo);。此时我们处于作用域g,并尝试访问在作用域f中声明的变量foo。但正如之前所述,我们可以访问任何在词法父函数中声明的变量,这在这里是成立的;gf的词法父级。因此打印出了hello
现在让我们看看代码行console.log(bar);。此时我们处于作用域f,并尝试访问在作用域g中声明的变量barbar没有在当前作用域中声明,且函数g不是f的父级,因此bar为未定义。

实际上,我们还可以访问词法“曾祖父”函数的作用域中声明的变量。因此,如果在函数g中定义了函数h,我们也可以访问在函数h中声明的变量。

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f
然后h将能够访问在函数hgf作用域中声明的所有变量。这是通过闭包实现的。在JavaScript中,闭包允许我们访问在词法父函数中声明的任何变量,以及在词法祖父函数、词法曾祖父函数等中声明的变量。
这可以看作是一个作用域链当前函数的作用域->词法父函数的作用域->词法祖父函数的作用域->...,一直到没有词法父级的最后一个父函数。 window对象 实际上,链并不会在最后一个父函数停止。还有一个特殊的作用域;全局作用域。在函数中未声明的每个变量都被认为是在全局作用域中声明的。全局作用域有两个特点:
  • 在全局作用域中声明的每个变量都可以随处访问
  • 在全局作用域中声明的变量对应于window对象的属性。
因此,在全局作用域中声明变量foo有两种方法;要么不在函数中声明它,要么设置window对象的属性foo两种方法都使用闭包 现在您已经阅读了更详细的说明,可能现在显然两种解决方案都使用闭包。但是为了确保,让我们进行证明。
让我们创建一种新的编程语言; JavaScript-No-Closure。如其名称所示,JavaScript-No-Closure与JavaScript相同,只是不支持闭包。
换句话说;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

好的,让我们看看使用JavaScript-No-Closure的第一个解决方案会发生什么;

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}
因此,在JavaScript-No-Closure中,这将打印十次undefined。因此第一个解决方案使用闭包。现在让我们看看第二个解决方案;
for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}
因此,这将在JavaScript-No-Closure中打印十次undefined。两种解决方案都使用闭包。编辑:假设这3个代码片段未在全局范围内定义。否则,变量fooi将绑定到window对象,并因此在JavaScript和JavaScript-No-Closure中通过window对象访问。

为什么 i 应该是未定义的?你只是引用了父作用域,如果没有闭包,它仍然有效。 - leemes
@leemes:我认为brillout.com试图解释的是:如果函数没有除自身之外的其他作用域(即,没有作用域链),那么i确实会是undefined,因为在外部函数执行时它是不可用的(尽管它在for循环中仍然被执行)。他的观点是:没有闭包,使用高阶函数变得非常有限。 - Abel
@Abel 是的,我明白了;但我们可以讨论一下我们能否访问 i 是因为 "闭包" 特性还是因为 JS 中 "变量作用域" 的工作方式,它从当前函数通过字面上的父函数一直到全局作用域。就像在 C/C++/Java(或类似语言)中,我们可以访问当前块、父块……直到函数作用域,然后是 OOP 语言中的类作用域,最后是一些全局作用域。只是在 JS 中,我们没有块,而是使用函数来作为变量命名空间的作用域。 - leemes
2
@leemes,我同意。与被接受的答案相比,这并没有真正展示发生了什么。 :) - Abel
3
我认为这是最好的答案,它通俗易懂地解释了闭包的一般性和简单性,并进入了特定的用例。谢谢! - tim peterson
显示剩余2条评论

23

我从来不满意任何人对此的解释。

理解闭包的关键是要了解没有闭包的情况下JS会如何运行。

如果没有闭包,这段代码会抛出一个错误

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

假设在一个虚拟的没有闭包的JavaScript版本中,一旦outerFunc返回,对outerVar的引用将被垃圾回收并消失,使内部函数无法继续引用。

闭包是一种特殊的规则,它使得内部函数可以引用外部函数的变量。通过使用闭包,这些被引用的变量即使在外部函数完成或“关闭”后也会保持不变。

即使使用闭包,如果一个函数没有内部函数引用其本地变量,那么该函数的本地变量的生命周期就像没有闭包的版本中的一样。当函数执行完毕时,本地变量会被垃圾回收。

然而,一旦内部函数引用了外部变量,就好像在这些被引用的变量上放了一根门槛,阻止了垃圾回收。

更准确地说,闭包是指内部函数基本上将内部作用域用作其自己的作用域基础。

但实际上,所引用的上下文是持久存在的,而不像快照一样。重复调用返回的内部函数,它会持续递增和记录外部函数的本地变量,并且会不断提醒更高的值。

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

你对于“快照”(我想你是指我的回答)是正确的。我在寻找一个能够表达这种行为的词语。在你的例子中,它可以被看作是一个“热链接”闭包结构。当将闭包作为参数传递给内部函数时,可以说它的行为就像是一个“快照”。但我同意,错误使用的词语只会让主题更加混乱。如果你有任何建议,我会更新我的回答。 - Andries
如果您将内部函数命名为一个命名函数,这可能有助于解释。 - Phillip Senn
没有闭包,你会得到一个错误,因为你试图使用一个不存在的变量。 - Ruan Mendes
嗯...说得好。引用未定义的变量是否曾经不会抛出错误,因为它最终会在全局对象上查找属性,还是我混淆了对未定义变量的赋值? - Erik Reppen

17

你们两个都在使用闭包。

这里我采用了维基百科的定义

在计算机科学中,闭包(也称词法闭包或函数闭包)是一个函数或对函数的引用和与之相关联的引用环境——存储该函数的每个非本地变量(也称自由变量)的引用的表。闭包——不像一个普通的函数指针——允许一个函数即使在其直接词法范围之外被调用时也能访问那些非本地变量。

你朋友的尝试明显使用了非本地变量i,通过取它的值并将其复制存储到本地变量i2中。

你自己的尝试将i(在调用点处在作用域中)作为参数传递给一个匿名函数。到目前为止这还不是闭包,但然后这个函数返回另一个引用相同i2的函数。由于在内部匿名函数中i2不是本地变量,因此这创建了一个闭包。


是的,但我认为重点在于他是如何做到的。他只是将i复制到i2,然后定义一些逻辑并执行此函数。如果我不立即执行它,而是将其存储在变量中,并在循环之后执行它,它会打印10,不是吗?所以它没有捕获i。 - leemes
6
它正确地捕获了变量 i。你所描述的行为并不是因为闭包与非闭包的区别,而是由于闭合变量在此期间被更改导致的。你正在使用不同的语法来执行相同的操作,即立即调用一个函数并将 i 作为参数传递(这会将其当前值立即复制)。如果您将自己的 setTimeout 放在另一个 setTimeout 中,同样的事情也会发生。 - Jon

13
你和你的朋友都在使用闭包:
闭包是一种特殊的对象,它结合了两个东西:一个函数和创建该函数时所在的环境。环境由在创建闭包时处于作用域内的任何局部变量组成。
MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Closures 在你朋友的代码中,函数function(){ console.log(i2); }定义在匿名函数function(){ var i2 = i; ... 的闭包中,并且可以读写局部变量i2
在你的代码中,函数function(){ console.log(i2); }定义在带有参数i2的函数function(i2){ return ...的闭包中,并且可以读写局部变量i2(在这种情况下声明为参数)。
在两种情况下,函数function(){ console.log(i2); }都传递给setTimeout
另一个等效但占用更少内存的方法是:
function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

1
我不明白为什么你的解决方案比我朋友的解决方案“更快且内存利用率更低”,能否详细说明一下? - brillout
3
在你的解决方案中,你创建了20个函数对象(每次循环创建2个对象:2x10=20)。你的朋友的解决方案也得到了同样的结果。在“我的”解决方案中,只创建了11个函数对象:1个在for循环前和10个“内部”创建的对象- 1 + 1x10 = 11。因此 - 内存使用减少,速度增加。 - Andrew D.
1
理论上来说,那是正确的。但在实践中也是如此:请参考这个JSPerf基准测试:http://jsperf.com/closure-vs-name-function-in-a-loop/2 - Rob W

11

闭包

闭包并不是一个函数,也不是一个表达式。它应该被视为从函数外部范围使用的变量的一种“快照”,并在函数内部使用。从语法上讲,应该说:“获取这些变量的闭包”。

换句话说,闭包是函数所依赖的相关变量上下文的副本。

再说一遍(简单易懂):闭包可以访问未作为参数传递的变量。

请注意,这些函数概念强烈依赖于您使用的编程语言/环境。在JavaScript中,闭包取决于词法作用域(大多数C语言也是如此)。

因此,返回函数通常是返回一个匿名/无名函数。当函数访问未作为参数传递的变量且在其(词法)范围内时,已经获取了闭包。

因此,关于您的示例:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

所有的代码都在使用闭包。不要将执行点和闭包混淆。如果在错误的时刻拍摄闭包的“快照”,那么值可能出乎意料,但肯定会使用闭包!


10

让我们来看看两种方式:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

声明并立即执行一个匿名函数,在其自己的上下文中运行setTimeout()。首先将当前值i复制到i2中以保存其状态;这是由于立即执行导致的。

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

声明一个执行上下文,使内部函数中的i当前值保留到i2中;这种方法还使用立即执行来保存该值。

重要提示

应该提到,两种方法之间的运行语义不同;您的内部函数传递给setTimeout(),而他的内部函数本身调用setTimeout()

将这两段代码都包裹在另一个setTimeout()中并不能证明只有第二种方法使用闭包,因为根本就没有相同的东西可言。

结论

两种方法都使用闭包,所以这取决于个人喜好;第二种方法更容易“移动”或泛化。


我认为区别在于:他的解决方案(第一个)是通过引用捕获,而我的(第二个)是通过值捕获。在这种情况下没有区别,但如果我将执行放在另一个setTimeout中,我们会看到他的解决方案存在问题,因为它使用了i的最终值而不是当前值,而我的解决方案仍然使用当前值(因为被值捕获)。 - leemes
让我看一下... 我想展示函数对象可以被传递,并且原始变量 i 可以被更改,而不会影响函数打印的内容,不取决于我们何时或在何处执行它。 - leemes
@leemes 正如之前所提到的,()正是使他的代码工作的关键,就像你的(i)一样;你不仅仅包装了他的代码,还对其进行了更改..因此,你不能再做出有效的比较了。 - Ja͢ck
@leemes 嗯,他的代码会将 undefined 传递给 setTimeout() 的第一个参数;话虽如此,两种方法的运行语义并不完全相同,这就是为什么你看到了不同的行为。 - Ja͢ck
由于我的变量是按值捕获/绑定的,而他是按引用绑定的,我们只有在存储并执行循环后生成的闭包时才能看到差异。这可以使用setTimeout来完成,但也可以通过将其存储在函数数组中并在之后执行它们来完成。这就是为什么我认为他的解决方案不是闭包,但实际上它确实是一个闭包;只是没有捕获变量,而是使用了另一种不同的方法。 - leemes
显示剩余3条评论

9

我之前写了一篇文章来帮助自己理解闭包以及它在JS中的工作原理。

闭包是一个函数,当被调用时,使用声明它的作用域而不是调用它的作用域。在JavaScript中,所有的函数都遵循这个规则。变量值在作用域中持续存在,只要还有一个函数仍然指向它们。唯一的例外是'this',它指的是函数被调用时所在的对象。

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 

6

仔细检查后,看起来你们两个都在使用闭包。

在你朋友的情况下,i 在匿名函数1中被访问,而 i2 在匿名函数2中被访问,在那里存在 console.log

在你的情况下,你正在匿名函数中访问 i2,其中存在 console.log。在 console.log 之前添加一个 debugger; 语句,并在 Chrome 开发者工具中的“作用域变量”下查看它将告诉变量所在的作用域。


2
右侧面板的“Closure”部分是因为没有更具体的名称可用。“Local”比“Closure”更强烈地指示。 - Rob W

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