JavaScript 闭包与匿名函数

JavaScript closures vs. anonymous functions

提问人:leemes 提问时间:10/17/2012 最后编辑:Azeezah Mleemes 更新时间:8/20/2020 访问量:120907

问:

我和我的一个朋友目前正在讨论JS中什么是闭包,什么不是。我们只是想确保我们真正正确地理解它。

让我们举个例子。我们有一个计数循环,并希望延迟在控制台上打印计数器变量。因此,我们使用 和 闭包来捕获计数器变量的值,以确保它不会打印值 N 乘以 N。setTimeout

没有闭包或任何接近闭包的错误解决方案是:

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

当然,这将打印 After the Loop 值的 10 倍,即 10。i

所以他的尝试是:

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

按预期打印 0 到 9。

我告诉他,他没有使用闭合来捕捉,但他坚持认为他是。我通过将 for 循环体放在另一个 (将他的匿名函数传递给 ) 来证明他不使用闭包,再次打印 10 乘以 10。如果我将他的函数存储在 a 中并在循环执行它,同样适用,也打印 10 乘以 10。所以我的论点是,他并没有真正捕捉到 i 的价值,因此他的版本不是一个闭包。isetTimeoutsetTimeoutvar

我的尝试是:

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

所以我捕获(在闭包中命名),但现在我返回另一个函数并传递它。就我而言,传递给 setTimeout 的函数确实捕获了 iii2

现在谁在使用闭包,谁没有?

请注意,两种解决方案在控制台上打印 0 到 9 都是延迟的,因此它们解决了原始问题,但我们想了解这两个解决方案中的哪一个使用闭包来实现此目的。

JavaScript 作用域 闭包

评论

15赞 Blender 10/17/2012
这些可能有用: “闭包”和“lambda”有什么区别? 和 什么是 Lambda?
1赞 Blender 10/17/2012
@leemes:请参阅我的忍者编辑以获取第二个链接。
2赞 brillout 10/17/2012
我们刚刚达成了一项协议:正确的人将获得与这个问题相关的 SO 积分
1赞 Aadit M Shah 10/17/2012
@leemes - 你们俩都在使用闭包。你们俩都做了两个功能——一个外部功能和一个内部功能;你的两个内在功能都是闭合的。您的所有函数都是 lambda(匿名函数)。有关详细信息,请阅读我的回答。
1赞 Aadit M Shah 10/17/2012
@blesh - 我不知道什么是修改后的闭包。我看到您的链接指向 C# 代码。JavaScript 是否支持修改后的闭包?

答:

6赞 Ramesh 10/17/2012 #1

仔细检查后,看起来你们俩都在使用闭合。

在您的朋友案例中,在匿名函数 1 中访问,并在存在 的匿名函数 2 中访问。ii2console.log

在您的情况下,您正在访问存在的匿名功能内部。在 chrome 开发者工具的“范围变量”下添加一条语句,它会告诉变量的作用域。i2console.logdebugger;console.log

评论

2赞 Rob W 10/17/2012
使用右侧面板中的“闭包”部分,因为没有更具体的名称。“本地”是比“关闭”更强的指示。
98赞 kev 10/17/2012 #2

根据定义:closure

“闭包”是一个表达式(通常是一个函数),它可以将自由变量与绑定这些变量的环境(“关闭”表达式)放在一起。

如果您定义一个函数,该函数使用在函数外部定义的变量,则使用。(我们称该变量为自由变量)。
他们都使用(即使在第一个示例中)。
closureclosure

评论

1赞 Jon 10/17/2012
第三个版本如何使用函数外部定义的变量?
1赞 kev 10/17/2012
@Jon返回的函数使用,该函数在外部定义。i2
1赞 internals-in 7/21/2013
@kev 如果您定义的函数使用函数外部定义的变量,则使用闭包......那么在“案例 1:你朋友的程序”中,Aadit M Shah 的答案是“函数 f”是闭包吗?它使用 i(在函数外部定义的变量)。全局作用域是否引用限定词?
13赞 Andrew D. 10/17/2012 #3

你和你的朋友都使用闭包:

闭包是一种特殊的对象,它结合了两件事:函数和创建该函数的环境。环境由创建闭包时范围内的任何局部变量组成。

MDN:https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

在你朋友的代码函数中定义了匿名函数的闭包,可以读/写局部变量 i2function(){ console.log(i2); }function(){ var i2 = i; ...

在您的代码函数中定义了函数的闭包,并且可以读/写本地有价值的 i2(在本例中声明为参数)。function(){ console.log(i2); }function(i2){ return ...

在这两种情况下,函数都会传入 .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 10/17/2012
我不明白为什么你的解决方案与我朋友的解决方案相比“更快,内存利用率更低”,你能详细说明一下吗?
3赞 Andrew D. 10/17/2012
在解决方案中,创建 20 个函数对象(每个循环 2 个对象:2x10=20)。同样的结果也适用于您的朋友的解决方案。在“my”解决方案中,只创建了 11 个函数对象:1 个 before for 循环和 10 个“inside” - 1+1x10=11。结果 - 更少的内存使用和速度的提高。
1赞 Rob W 10/17/2012
从理论上讲,这是真的。在实践中,还可以: 请参阅以下 JSPerf 基准测试: jsperf.com/closure-vs-name-function-in-a-loop/2
17赞 Jon 10/17/2012 #4

你们俩都在使用闭包。

我在这里使用维基百科的定义

在计算机科学中,闭包(也称为词法闭包或函数 closure) 是一个函数或对函数的引用,以及 引用环境 - 存储对每个 该函数的非局部变量(也称为自由变量)。 与普通函数指针不同,闭包允许函数访问 这些非局部变量,即使在其直接之外调用时也是如此 词法范围。

你的朋友的尝试显然使用了变量 ,它是非局部的,通过获取它的值并制作一个副本来存储到本地 .ii2

您自己的尝试将(在调用站点的范围内)作为参数传递给匿名函数。到目前为止,这不是一个闭包,但随后该函数返回另一个引用相同 .由于内部匿名函数内部不是本地函数,因此会创建一个闭包。ii2i2

评论

0赞 leemes 10/17/2012
是的,但我认为关键是他是如何做到的。他只是复制到 ,然后定义一些逻辑并执行这个函数。如果我不立即执行它,而是将其存储在 var 中,并在循环后执行它,它会打印 10,不是吗?所以它没有捕获 i。ii2
6赞 Jon 10/17/2012
@leemes:它拍得刚刚好。您所描述的行为不是闭合与非闭合的结果;这是同时更改闭合变量的结果。您使用不同的语法做同样的事情,立即调用函数并作为参数传递(当场复制其当前值)。如果你把自己的放在另一个里面,同样的事情也会发生。iisetTimeoutsetTimeout
10赞 Ja͢ck 10/17/2012 #5

让我们看看这两种方式:

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

声明并立即执行在其自己的上下文中运行的匿名函数。通过将 的当前值复制到 first 来保留;它之所以有效,是因为立即执行。setTimeout()ii2

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

声明内部函数的执行上下文,其中当前值 的保留在 ;此方法还使用立即执行来保留值。ii2

重要

应该提到的是,两种方法之间的运行语义并不相同;你的内在功能被传递给他,而他的内在功能则调用自己。setTimeout()setTimeout()

将两个代码包装在另一个代码中并不能证明只有第二种方法使用闭包,只是一开始就不是一回事。setTimeout()

结论

这两种方法都使用闭合,因此归结为个人品味;第二种方法更容易“移动”或概括。

评论

0赞 leemes 10/17/2012
我认为区别在于:他的解决方案(第一个)是通过引用捕获的,我的(第二个)是按值捕获的。在这种情况下,它没有区别,但是如果我将执行放在另一个 setTimeout 中,我们会看到他的解决方案存在一个问题,即它使用 i 的最终值,而不是当前值,而我的门槛使用当前值(因为被值捕获)。
0赞 Ja͢ck 10/17/2012
@leemes 你们俩都以同样的方式捕捉;通过函数参数或赋值传递变量是一回事......您能否在您的问题中补充一下,您将如何将执行包装在另一个中?setTimeout()
0赞 leemes 10/17/2012
让我看看这个......我想证明函数对象可以传递,原始变量可以更改,而不会影响函数打印的内容,而不取决于我们执行它的位置或时间。i
0赞 leemes 10/17/2012
等等,你没有把函数传递给(外部的)setTimeout。删除这些 ,从而传递一个函数,你会看到 10 倍的输出。()10
0赞 Ja͢ck 10/17/2012
@leemes 如前所述,正是使他的代码工作的原因,就像你的代码一样;你不只是包装他的代码,你对它进行了更改......因此,您无法再进行有效的比较。()(i)
55赞 12 revsbrillout.com #6

简而言之,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?

现在让我们考虑一下我们在另一个函数中定义了一个函数的情况。gf

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

我们将 的词法父级称为 。 如前所述,我们现在有 2 个范围;范围和范围.fgfg

但是一个作用域“在”另一个作用域内,那么子函数的作用域是父函数作用域的一部分吗?在父函数的作用域中声明的变量会发生什么情况;我是否能够从子函数的范围内访问它们? 这正是关闭介入的地方。

闭 包

在JavaScript中,该函数不仅可以访问在作用域中声明的任何变量,还可以访问在父函数的作用域中声明的任何变量。ggf

考虑以下;

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);gfoofgfhelloconsole.log(bar);fbargbargfbar

实际上,我们还可以访问在词法“大父级”函数范围内声明的变量。因此,如果函数中定义了一个函数hg

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

然后将能够访问函数 、 和 的作用域中声明的所有变量。这是通过闭包完成的。在 JavaScript 闭包中,我们允许访问在词法父函数、词法父函数、词法父函数等中声明的任何变量。 这可以看作是一个范围链; 直到最后一个没有词法父函数的父函数。hhgf scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...

window 对象

实际上,链不会在最后一个父函数处停止。还有一个特殊的范围;全局范围。未在函数中声明的每个变量都被视为在全局作用域中声明。全球范围有两个专业;

  • 在全局范围内声明的每个变量都可以在任何地方访问
  • 在全局作用域中声明的变量对应于对象的属性。window

因此,在全局范围内声明变量的方法正好有两种;要么不在函数中声明它,要么设置 window 对象的属性。foofoo

两次尝试都使用闭包

现在,您已经阅读了更详细的说明,现在很明显,这两种解决方案都使用闭包。 但可以肯定的是,让我们做一个证明。

让我们创建一种新的编程语言;JavaScript-no-closure。 顾名思义,JavaScript-No-Closure 与 JavaScript 相同,只是它不支持 Closures。

换句话说;

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 中打印 10 次。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 中打印 10 次。undefined

这两种解决方案都使用闭包。

编辑:假设这 3 个代码片段未在全局范围内定义。否则,变量 和 将绑定到对象,因此可以通过 JavaScript 和 JavaScript-No-Closure 中的对象访问。fooiwindowwindow

评论

0赞 leemes 10/17/2012
为什么应该未定义?您只需引用父范围,如果没有闭包,该范围仍然有效。i
0赞 brillout 10/17/2012
原因与 foo 在 JavaScript-No-Closure 中未定义的原因相同。<code>i</code> 在 JavaScript 中不是未定义的,这要归功于 JavaScript 中允许访问词法父级中定义的变量的功能。此功能称为闭包。
0赞 leemes 10/17/2012
您不明白引用已定义的变量和自由变量之间的区别。在闭包中,我们定义了自由变量,这些变量必须在外部上下文中绑定。在代码中,只需在定义函数时设置为。这使得 NOT 成为自由变量。尽管如此,我们仍然认为您的函数是一个闭包,但没有任何自由变量,这就是重点。i2ii
2赞 Abel 10/25/2012
@leemes,我同意。与公认的答案相比,这并不能真正显示实际发生的事情。:)
3赞 tim peterson 3/18/2013
我认为这是最好的答案,一般而简单地解释闭包,然后进入具体的用例。谢谢!
11赞 Andries 10/17/2012 #7

关闭

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

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

再一次(幼稚):闭包可以访问未作为参数传递的变量。

请记住,这些功能概念很大程度上取决于您使用的编程语言/环境。在 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)
}

所有这些都使用闭包。不要将执行点与闭包混淆。如果在错误的时间拍摄了闭包的“快照”,则值可能是意想不到的,但肯定会采取闭包!

672赞 Aadit M Shah 10/17/2012 #8

编者按: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. 因此没有被关闭if
    2. 因此没有被关闭setTimeoutf
    3. 因此没有被关闭consolef

因此,该函数不是闭包。f

为:g

  1. 列出变量:
    1. console是一个自由变量。
    2. i2是一个自由变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. console绑定到全局范围。
    2. i2绑定到 的范围。f
  3. 该函数在哪个作用域中引用setTimeout 的作用域
    1. 因此没有被关闭consoleg
    2. 因此被 关闭i2g

因此,当自由变量从内部引用,该函数是自由变量的闭包(它是 的上值)。gi2gsetTimeout

对你不利:您的朋友正在使用闭包。内部功能是闭合。

案例 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. 因此没有被关闭consolef

因此,该函数不是闭包。f

为:g

  1. 列出变量:
    1. console是一个自由变量。
    2. i2是一个自由变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. console绑定到全局范围。
    2. i2绑定到 的范围。f
  3. 该函数在哪个作用域中引用setTimeout 的作用域
    1. 因此没有被关闭consoleg
    2. 因此被 关闭i2g

因此,当自由变量从内部引用,该函数是自由变量的闭包(它是 的上值)。gi2gsetTimeout

对你有好处:您正在使用闭包。内部功能是闭合。

所以你和你的朋友都在使用闭包。别再吵了。我希望我清除了闭包的概念以及如何为你们俩识别它们。

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

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

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. 当我们执行程序时,我们希望收到警报,因为它不是闭包(即它可以访问其父范围内的所有变量 - 包括 )。messageregularFunctionmessage
  3. 当我们执行程序时,我们观察到它确实被警告了。message

接下来,让我们考虑以下程序(这是替代方案):

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. 当我们执行程序时,我们希望不会收到警报,因为是一个闭包(即它只能在创建函数时访问其所有非局部变量请参阅此答案) - 这不包括 )。messageclosureFunctionmessage
  3. 当我们执行程序时,我们观察到它实际上被警告了。message

我们从中推断出什么?

  1. JavaScript 解释器处理闭包的方式与处理其他函数的方式不同。
  2. 每个函数都带有其作用域链。闭包没有单独的引用环境。
  3. 闭包就像其他所有函数一样。当它们在它们所属范围之外的范围内被引用时,我们只称它们为闭包,因为这是一个有趣的情况。

评论

42赞 leemes 10/17/2012
被接受,因为你非常详细,很好地解释了正在发生的事情。最后,我现在更好地理解了什么是闭包,或者更好地说:变量绑定在 JS 中是如何工作的。
3赞 rosscj2533 10/17/2012
在案例 1 中,您说它在 的范围内运行,但在案例 2 中,您说它在全局范围内运行。它们都在 setTimeout 中,那么有什么区别呢?gsetTimeoutf
11赞 Briguy37 10/18/2012
你能说明你的来源吗?我从未见过一个定义,如果在一个作用域中调用函数,而不是在另一个作用域中调用,则函数可能是闭包。因此,这个定义似乎是我习惯的更一般定义的子集(参见 kev 的回答),其中闭包就是闭包,无论它被称为什么范围,或者即使它从未被调用!
13赞 Peter 10/19/2012
@AaditMShah 我同意你关于什么是闭包的看法,但你说好像 JavaScript 中的常规函数和闭包之间是有区别的。没有区别;在内部,每个函数都将带有对创建它的特定作用域链的引用。JS 引擎不认为这是一个不同的情况。不需要复杂的清单;只要知道每个函数对象都带有词法范围即可。变量/属性全局可用的事实并没有使函数减少闭包(这只是一个无用的情况)。
15赞 Aadit M Shah 10/19/2012
@Peter - 你知道吗,你是对的。常规函数和闭包之间没有区别。我运行了一个测试来证明这一点,结果对你有利:这是控件,这是替代方案。你说的确实有道理。JavaScript 解释器需要对闭包进行特殊的簿记。它们只是具有第一类函数的词法范围语言的副产品。我的知识仅限于我读到的内容(这是错误的)。谢谢你纠正我。我将更新我的答案以反映相同的内容。
23赞 Erik Reppen 10/18/2012 #9

我从来不对任何人解释这一点的方式感到满意。

理解闭包的关键是了解没有闭包的 JS 会是什么样子。

如果没有闭包,这将引发错误

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

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

一旦 outerFunc 返回了一个虚构的闭包禁用版本的 JavaScript,对 outerVar 的引用将被垃圾收集并消失,不留下任何内容供内部函数引用。

闭包本质上是一种特殊的规则,当内部函数引用外部函数的变量时,这些变量可能会存在。使用闭包时,即使在外部函数完成或“闭包”(如果这有助于记住要点)之后,引用的变量也会保持不变。

即使使用闭包,在没有引用其局部函数的函数的函数中,本地变量的生命周期也与无闭包版本中的生命周期相同。当函数完成时,当地人会收集垃圾。

然而,一旦你在内部函数中引用了外部变量,就像门框被放在了那些引用的变量的垃圾收集中一样。

一个也许更准确的方法来看待闭包,是内部函数基本上使用内部作用域作为它自己的作用域基础。

但引用的上下文实际上是持久的,不像快照。反复触发返回的内部函数,该函数不断递增并记录外部函数的本地 var 将不断提醒更高的值。

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

评论

0赞 Andries 10/25/2012
你对“快照”的看法是正确的(我认为,你参考了我的答案)。我一直在寻找一个可以表示行为的词。在您的示例中,它可以看作是“热链接”闭包结构。当将闭包捕获为内部函数中的参数时,可以说它的行为类似于“快照”。但我同意,误用的词语只会增加主题的混乱。如果您对此有任何建议,我将更新我的答案。
0赞 Phillip Senn 3/19/2013
如果你给内部函数是一个命名函数,它可能有助于解释。
0赞 Ruan Mendes 7/2/2015
如果没有闭包,您将收到错误,因为您尝试使用不存在的变量。
0赞 Erik Reppen 7/6/2015
嗯。。。好点子。引用未定义的 var 是否从未抛出错误,因为它最终会查找为全局对象上的属性,或者我是否与对未定义变量的赋值感到困惑?
9赞 Nat Darke 12/22/2012 #10

我不久前写了这篇文章,以提醒自己什么是闭包以及它在 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' 
4赞 ekim 3/15/2015 #11

请考虑以下几点。 这将创建并重新创建一个函数,该函数关闭于 ,但不同的函数!fi

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

而以下内容在“a”函数“本身”
( 他们自己! 之后的片段使用单个指称
f )

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

或者更明确地说:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

铌。打印 is before 的最后一个定义。ffunction(){ console.log(9) }0

警告!闭包概念可能会强制分散对基本编程本质的注意力:

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

x-refs.:
JavaScript 闭包是如何工作的?
Javascript 闭包解释
(JS) 闭包是否需要函数内部的函数
如何理解 Javascript 中的闭包?
Javascript 局部变量和全局变量混淆

评论

0赞 ekim 3/15/2015
第 1 次尝试的片段 - 不确定如何控制 - 复制'Run' only was desired - not sure how to remove the
-1赞 Eunjung Lee 5/12/2018 #12

我想分享我的例子和关于闭包的解释。我做了一个 python 示例,以及两个图来演示堆栈状态。

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

此代码的输出如下所示:

*****      hello      #####

      good bye!    ♥♥♥

下面是两个图,分别显示堆栈和附加到函数对象的闭包。

当函数从 Maker 返回时

稍后调用函数时

当通过参数或非局部变量调用函数时,代码需要局部变量绑定,例如 margin_top、填充以及 a、b、n。为了保证函数代码的工作,应该可以访问很久以前消失的 maker 函数的堆栈帧,该帧与函数消息对象一起备份在闭包中。

评论

0赞 Eunjung Lee 5/12/2018
我想删除这个答案。我意识到这个问题不是关于什么是闭合,所以我想把它移到另一个问题上。
2赞 Rory McCrossan 6/11/2019
我相信你有能力删除自己的内容。单击答案下方的链接。delete