我和我的一个朋友正在讨论什么是JS中的闭包,什么不是。我们只是想确保我们真正理解它。
让我们以这个例子为例。我们有一个计数循环,希望延迟在控制台上打印计数器变量。因此,我们使用setTimeout和闭包捕获计数器变量的值,以确保它不会打印n倍的值n。
没有闭包或任何接近闭包的错误解决方案是:
1 2 3 4 5
| for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
} |
当然,它会在循环后打印10倍于i的值,即10。
所以他的尝试是:
1 2 3 4 5 6 7 8
| for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
} |
按预期打印0到9。
我告诉他,他并没有利用一个关闭来抓捕埃多克斯(1),但他坚持说他是。我通过将for循环体放入另一个setTimeout中(将匿名函数传递给setTimeout)证明了他不使用闭包,再次打印10次10。如果我将他的函数存储在一个var中,并在循环后执行它,同样适用,也可以打印10次10。所以我的观点是,他并没有真正抓住i的价值,使得他的版本不是一个终结。
我的尝试是:
1 2 3 4 5 6 7
| for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
} |
所以我在闭包中捕获了i(名为i2),但现在我返回另一个函数并传递这个函数。在我的例子中,传递给settimeout的函数确实捕获了i。
现在谁在使用闭包,谁没有?
请注意,这两个解决方案在控制台上打印0到9都延迟了,因此它们解决了最初的问题,但我们想了解这两个解决方案中的哪一个使用闭包来完成这一点。
- 这些可能有用:闭包和lambda之间有什么区别?什么是lambda?
- @搅拌机谢谢你的链接。我认为他使用lambda函数,而我使用闭包。这是正确的吗?
- @利默斯:看我的忍者编辑第二个链接。
- 我们刚刚达成了一个协议:正确的人会得到与这个问题相关的so点。
- @利默斯:我觉得你的尝试实际上并不是一个终结。您正在通过一个参数传入i,这与从父级的作用域获取该参数不同。
- @Blender my闭包是返回的函数,而不是包装器。
- @利默斯-你们都在使用闭包。你们两个都做了两个函数——一个外部函数和一个内部函数;你们两个内部函数都是闭包。所有函数都是lambda(匿名函数)。详细内容请阅读我的答案。
- @brillout.com-既然你们两个平手,我可以得到与这个问题相关的so点吗?
- @啊,我会给你一些赏金的,但这只能在两天内实现。
- 术语"闭包"和"修改闭包"有时会在开发人员之间造成语义混乱。因为很多人都会遇到由"访问修改后的闭包"引起的错误,这会使他们认为"闭包"与实际情况完全不同。
- 在javascript中有一种更好的方法可以做到这一点,但它并不完全涉及闭包:for(...) { with({i: i}) { setTimeout(function() {console.log(i);}, 1000); } }。
- @我不知道修改后的闭包是什么。我看到你的链接指向C代码。javascript是否支持修改后的闭包?
- 修改后的闭包有点用词不当。基本上,OP的第一个示例说明了一个修改后的闭包。也就是说,在某些内部方法访问之前,有一些自由变量被更改了。例如,在第一个示例中,从for循环访问的i。在执行任何超时回调时,它被修改为10。
- 事实上,这似乎是JetBrains在R中引入的术语。但我知道有人用它来描述其他语言的相同情况。
- @伊兹卡塔-这是有争议的。使用with为作用域链添加了一个新的词汇作用域。这类似于调用新函数。记住,"闭包"是一个表达式(通常是一个函数),它可以将自由变量与绑定这些变量的环境(即"闭包"表达式)结合在一起。这个"环境"必须是什么,还没有具体定义。因此,我认为可以安全地假设,即使使用with而不是外部函数,也会创建一个闭包。直观地说,即使是全局范围也不是函数的范围。它只是一个词汇范围,就像with一样。
- @布雷斯-啊。这是有道理的。初学者的经典javascript问题。所以,如果我没有弄错的话,语言支持修改后的闭包的标准是它不能支持块范围界定。我说的对吗?
- 它必须支持某种词法范围。
- @aaditmshah我不知道该如何调用闭包和不该如何调用闭包,但我认为使用with并不是,即使它启动了一个新的作用域,因为在这个示例中,您处于一个匿名散列/对象的作用域中。据我所知,通常对象级作用域不算。或者对象(不是一系列表达式,而是属性的散列)是表达式本身?=)
- @BLESH-词汇作用域,无任何块作用域。就像在javascript中一样。=)
- @当你使用with时,你的词汇量是适当的。如果您在"对象范围"中,那么您将只能访问对象的属性(在您的情况下,只有i和对象原型链上的属性(这意味着您将能够直接访问hasOwnProperty之类的内容,这显然是错误的行为)。您将无法访问父范围中的变量,如setTimeout(您正在访问),因为它们不在传递给with的对象的原型链上。这就是为什么我相信这仍然是一个终结。
- @Izkata-你刚刚给了我一个很好的主意,用with在JavaScript中实现沙盒。让我们看看我要去哪里。=)
- +今天有一个疑问!
- 最好的问题是谁在使用"生命"
编者按:正如本文所解释的,javascript中的所有函数都是闭包。然而,我们只关心从理论的角度识别这些函数的一个子集。此后,除非另有说明,否则对闭包一词的任何引用都将引用该功能子集。好的。
闭包的简单解释:好的。
接受一个功能。我们称之为F。
列出f的所有变量。
变量可以有两种类型:
局部变量(绑定变量)
非局部变量(自由变量)
如果f没有自由变量,那么它就不能是一个闭包。
如果f有任何自由变量(在f的父范围中定义),则:
自由变量绑定到的f的父作用域必须只有一个。
如果从父范围之外引用f,则它将成为该自由变量的闭包。
这个自由变量被称为闭包f的upvalue。
现在让我们用这个来确定谁使用闭包,谁不使用闭包(为了解释,我已经命名了函数):好的。
案例1:你朋友的计划好的。
1 2 3 4 5 6 7 8
| for (var i = 0; i < 10; i++) {
(function f() {
var i2 = i;
setTimeout(function g() {
console.log(i2);
}, 1000);
})();
} |
在上述程序中有两个功能:f和g。让我们看看它们是否是闭包:好的。
对于f:好的。
列出变量:
i2是一个局部变量。
i是一个自由变量。
setTimeout是一个自由变量。
g是一个局部变量。
console是一个自由变量。
查找每个自由变量绑定到的父作用域:
i受全球范围的约束。
setTimeout受全球范围的约束。
console受全球范围的约束。
函数引用的范围是什么?全球范围。
因此,i不是由f关闭的。
因此,setTimeout不是由f关闭的。
因此,console不是由f关闭的。
因此,函数f不是一个闭包。好的。
对于g:好的。
列出变量:
console是一个自由变量。
i2是一个自由变量。
查找每个自由变量绑定到的父作用域:
console受全球范围的约束。
i2属于f的范围。
函数引用的范围是什么?setTimeout的范围。
因此,console不是由g关闭的。
因此,i2被g关闭。
因此,当从setTimeout中引用时,函数g是自由变量i2的闭包(它是g的一个upvalue)。好的。
对你不好:你的朋友正在使用一个闭包。内部功能是一个闭包。好的。
案例2:你的计划好的。
1 2 3 4 5 6 7
| for (var i = 0; i < 10; i++) {
setTimeout((function f(i2) {
return function g() {
console.log(i2);
};
})(i), 1000);
} |
在上述程序中有两个功能:f和g。让我们看看它们是否是闭包:好的。
对于f:好的。
List the variables:
字母名称0是地方变量。
字母名称是地方变量。
EDOCX1是一个自由变量。
Find the parent scope to which each free variable is bound:
爱多克X1是通往全球范围的通道
在什么范围是功能参考?The Global Scope.
Hence EDOCX1(英文)2[英文]5[英文]5[英文]
(P)如果这个函数是EDOCX1,那么它不是一个关闭。好的,好的。(P)致EDOCX1:好的,好的。
List the variables:
EDOCX1是一个自由变量。
字母名称0是一个自由变量。
Find the parent scope to which each free variable is bound:
爱多克X1是通往全球范围的通道
EDOCX1 0是一个充满活力的音乐剧5
在什么范围是功能参考?EDOCX1的音标13
Hence EDOCX1(英文)2[英文]2[英文]
伊多克x1的英文字母是用伊多克x1封杀的。
(P)Thus the function EDOCX1 pental 1 is a closure for the free variable EDOCX1 universal 0(which is an upvalue for EDOCX1 penal 1),when it's referenced from within EDOCX1 penal 13.好的,好的。(P)Good for you:You are using a closure.功能本身就是一种封闭。好的,好的。(P)So both you and your friend are using closure.停止Arguing。I hope I cleared the concept of closure and how to identify them for the both of you.好的,好的。(P)Edit:a simple explanation as to why are all functions closure(credits@Peter):好的,好的。(P)3.First let's consider the following program(it's the control):好的,好的。(P)字母名称好的,好的。
We know that both EDOCX1 plographic 22 welcx1 and EDOCX1 commercial 23 nable Aren't closures from the above definition.
When we execute the program we expect EDOCX1 single 24 to be alerted because EDOCX1 pental 23 communal is not a closure(i.e.it has access to all the variables in its parent scope-including EDOCX1 penal 24).
When we execute the program we observe that EDOCX1 disciplinary 24 nable is indeed alert.
(P)Next let's consider the following program(it's the alternative):好的,好的。(P)字母名称好的,好的。
We know that only EDOCX1 pental 28 is a closure from the above definition.
When we execute the program we expect EDOCX1 plography 24 sinki not to be alerted because EDOCX1 pental 28)is a closure(i.e.it only has access to all its non-local variables at the time the function is created(see this answer)-this does not include EDOCX1.
When we execute the program we observe that EDOCX1 single 24 is actually being alert.
(P)我们到底是怎么了?好的,好的。
Javascript interpreters do not treat closure differently from the way they treat other functions.
每个函数都有自己的范围Chain along with it.Closures don't have a separate referenceing environment.
a closure is just like every other function.We just call them closure when they are referenced in a scope outside the scope to which they belong because this is an interesting case.
好吧。
- 接受是因为你非常详细地解释了正在发生的事情。最后,我现在更好地理解了闭包是什么,或者更好地说:变量绑定如何在JS中工作。
- 在案例1中,你说g在setTimeout的范围内运行,但在案例2中,你说f在全球范围内运行。它们都在设定时间内,那么有什么区别呢?
- 如果1 g作为参数传递给setTimeout。因此,g在setTimeout的范围内执行。在情况2中,f似乎作为参数传递给setTimeout,但请记住,f是用参数i立即调用的。因此,它是在全球范围内执行的,由f返回的值(即g)实际上是作为参数传递给setTimeout。因此,g在setTimeout的范围内执行。
- @啊,啊,我明白了,很好的解释和答案。明天我会投票,这样你就能得到声誉了:)
- 请你说明一下你的资料来源好吗?我从未见过一个定义,如果在一个范围内调用函数,而不是在另一个范围内调用函数,那么它可以是一个闭包。因此,这个定义看起来像是更一般定义的一个子集(见kev的答案),其中闭包是闭包,不管它被调用的范围是什么,或者即使它从未被调用!
- @布里吉37-你是对的。虽然函数在另一个作用域中被调用时是一个闭包,但在被另一个作用域简单引用时也是一个闭包。因为引用比调用更普遍,所以我编辑了我的答案来反映它。现在我的定义应该和kev的答案一样。感谢您指出错误。感激。
- 我认为,正如通常所定义的术语,第一个程序中的f实际上比i更接近。也就是说,f实际上是对i的封闭。
- @丹夫兹-我不知道怎么做。变量i是一个全局变量,f本身存在于全局范围内。如果我们把f移出全球范围(或许换个窗口来说),这将是一个终结。这方面的一个例子类似于window.top.f()。你对此有何看法?你能解释一下你是怎么得出这个结论的吗?
- 这个答案继续传播有关JavaScript中闭包的错误信息。javascript中的每个函数在创建期间都会形成一个闭包。创建函数时,引用环境始终使用所谓的范围链作为函数对象的一部分存储。我认为人们混淆了闭包何时有用与何时不有用之间的区别。
- @彼得-我不同意。javascript中的变量的生存期仅限于它们所绑定的范围。因此,当一个变量超出范围时,它就不再存在。javascript中的函数引入了一个新的词汇范围。因为javascript有第一类函数,所以它们可以像变量一样传递。这很有趣,因为您基本上是在传递一个范围以及与之相关的所有内容。当一个函数在声明它的同一个范围内被引用时,就没有问题了。当它超出范围时,它需要带上它的范围。这叫做结束。
- @彼得-如果你想要证据,那么查看下面的MDN链接。在第五段中,它清楚地指出,"解决这个难题的办法是,myFunc已经成为一个终结"。"已成为"一词显然意味着,在给定的上下文之前,myFunc不是一个结束语。我希望我已经说服了你,我的答案不是散布关于JavaScript中闭包的错误信息。
- @peter-只是为了区分:作用域链是一系列执行上下文,将作用域中的每个局部变量存储为一个属性。闭包是一个单独的执行上下文(它只存储闭包的局部变量)以及一个引用环境(它是一个只包含对那些非局部闭包变量的引用的表)。更多信息请参见乔恩的回答。
- +一个简单的解释,滚动这么多。
- @aaditmshah对于闭包是什么,我同意您的看法,但您所说的好像在JavaScript中常规函数和闭包之间存在差异。没有区别;在内部,每个函数都将带有对创建它的特定范围链的引用。JS引擎并不认为这是另一种情况。不需要一个复杂的检查表;只要知道每个函数对象都带有词汇范围。变量/属性是全局可用的这一事实并不能使函数更像一个闭包(这只是一个无用的例子)。
- @这里的aaditmshah是一个不错的概述,介绍了一些关于闭包的常见神话,并引用了ECMA-262。javascriptweblog.wordpress.com/2010/10/25/&hellip;
- @彼得-你知道吗,你是对的。正则函数和闭包没有区别。我做了一个测试来证明这一点,结果对你有利:这里是控制,这里是备选方案。你说的确实有道理。JavaScript解释器需要为闭包做特殊的簿记。它们只是具有第一类函数的词汇范围语言的副产品。我的知识仅限于我所读的内容(这是错误的)。谢谢你纠正我。我会更新我的答案以反映同样的情况。
- @aaditmshah不用担心,这里有很多关于关闭的令人困惑的信息。我喜欢您将闭包描述为"具有第一类函数的词汇范围语言的副产品":)。当你这样想的时候,它并不是那么复杂。你引用的关于范围链的伟大文章,方式是:dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain
- @彼得-既然我的回答没有散布错误的信息,你介意取消投票吗?
- @啊,我不能,因为某种原因它还是锁着的:哦。
- @彼得-我编辑了我的答案。它还锁着吗?
- 谢谢你给我们看你的忍术
- @法马托-忍者节?我应该把这当作赞美吗?
- 对!!它帮助了我!谢谢!
- @Aadimshah-非常好,非常全面的文章。你能在5(i)中解释这一行吗-"自由变量必须只有一个F的父作用域。"我有点困惑,但你能举一个F的两个父作用域的例子吗?谢谢。
- @Anmolsaraf——它只是意味着一个变量只在一个范围内局部存在。例如,在第一个程序中,考虑函数g。变量i2是g中的自由变量(这意味着它在g的父范围中声明)。变量只声明一次。在这种情况下,i2在f中声明,它是g的父范围。因此,i2只是f的局部。它不存在于f的任何父范围中,也不是任何f的子集(如g的子集)中的局部变量(而是自由变量)。因此,如果一个变量在给定的范围内是自由的,那么它只在一个父范围内是本地的。
- @aaditmshah-感谢您的谦逊和详细的回复。从上面的行中再查询一次,"如果一个变量在给定的范围内是空闲的,那么它只是一个父范围的本地变量。"父范围必须是直接父范围,还是可以是直接父范围(即祖先)的父范围,例如,它是三个函数相互嵌套的情况?示例-函数B中的函数C和函数A中的函数B,那么函数A现在可以成为函数C中自由变量的父范围了吗?
- @Anmolsaraf-它可以是任何父范围。因此,如果h在g内,g在f内,而h中使用的是f的局部变量x,那么当h在f外被引用时,h只在x上关闭。
- @啊,谢谢你!!我现在很明白了:)
- 实际上,"javascript中的所有函数都是闭包",C中的所有函数都是闭包。C中的全局范围是一个变量环境,所有函数都可以看到自己的变量和全局环境的变量。全局环境的生命周期是整个程序的生命周期,因此垃圾收集的挑战就消失了。此外,全局环境只有一个实例,因此不需要每个函数调用存储指向它的指针。这是一个实现成本为零的关闭案例,这就是为什么它是C支持的唯一类型。
- 全局范围引用是否影响关闭属性?
- @7-不坏,我不完全确定您要问什么,但是函数永远不能关闭全局变量,因为全局范围是最顶部的范围。因此,永远不能在全局范围之外引用函数。
- + 1哦!我要来回走到现在,把这个概念融入……。知道了!谢谢任何方式
- 脱帽致敬,精彩的解释
- @在Lua等专门处理闭包的语言中,确定闭包实际上非常重要。ecmascript标准采用了简单的方法,使每个函数都携带着它的词汇环境。结果是闭包在JavaScript中并不有趣。然而,闭包的概念超越了JavaScript本身,理解闭包对于理解其他事情(如Hindley-Milner类型推断)是必不可少的。闭包应用的典范是Haskell的runst函数,它使用存在主义的量化。
- @阿德:你说的"结束"是什么意思?对于case1和函数g,自由变量i2被限定为f的范围,函数g被限定为范围设置超时。在这种情况下,I2是如何被g关闭的。请你再多说几句。
- @aadit:我无法识别I2是如何被g关闭的。I2被限定在f的范围内,g在scope settimeout中被引用。显然,F和settimeout的范围是相同的。请建议。
- @Shirgillansari,f和setTimeout的范围不一样。在案例1和函数g中,自由变量i2定义在f的范围内。因此,它与f绑定。函数g也在f的范围内定义。因此,它可以访问变量i2。但是,函数setTimeout不能访问变量i2,因为它不在f的范围内定义。f和setTimeout的作用范围不同,因为它们是两种不同的功能。因此,当我们将g传递给setTimeout时,我们将把g移出f的范围,移动到setTimeout中。
- @Shirgillansari因为我们将g移出f的范围,所以自由变量i2成为g的一个上值。函数g必须访问i2,即使函数setTimeout没有访问i2。函数setTimeout可能永远不会调用g(例如setTimeout(g, oneYear),但因为它现在可以访问g和g访问i2,因此变量i2不能被垃圾收集,因为将来使用setTimeout可能会称为g。本质上,由于函数f已完成执行,因此应该对i2进行垃圾收集。但事实并非如此。g正在关闭。
- @aadit:我不同意你说的第一句话:"f和settimeout的范围不一样,因为它们是两个不同的函数。"显然,f在全局范围(window)中被引用,而setTimeout是一个在window frame中声明的函数。因此,范围是相同的。
- @Shirgillansari我认为你不明白函数的作用域是什么。例如,考虑到function g() { function f(x) { return x + 1; } function setTimeout(x, y) { return x + y; } },f的范围是{ return x + 1; },setTimeout的范围是{ return x + y; }。它们都是在g的范围(对于全局)中定义的,但这并不意味着它们是相同的范围。它们是属于两个不同功能的两个不同范围。不同的函数不能具有相同的作用域。时期。希望有帮助。这个答案有458张赞成票。每个人都错了吗?我不这么认为。
- @阿德:现在同意了,但我认为我没有错。当您在最后一条评论中说,对于两个函数f和setTimeout,"它们都是在g(全局)的范围内定义的"。我指的是相同的引用范围。即(g)。对于这458张选票,即使在通过答案之前,我已经在仅仅看答案的结构时投了反对票。再次感谢。2个函数总是有不同的作用域,即使引用它们的作用域可能相同。
- 有趣的细节,但仍然很难理解!到目前为止,我的结论是:每当您在另一个函数中拥有一个函数时,您都使用闭包,并且可以使用它的特性。无论是返回封闭函数来传递它,还是只要调用封闭函数就执行它。我希望我不会离这个概念太远…
- 这应该被提名为史上最好的stackoverflow答案。
- @彼得,我可能是错的,但我敢肯定,对于不使用eval的函数,现代的vm只保留对自由变量的引用,函数实际上会关闭,而不是封闭范围中的所有变量,以提高性能。如果您试图将鼠标移到当前函数未关闭的非全局变量上(调试器无法获取其值),则可以在V8调试器中看到这一点。
- @安迪,哇,这么多年后才赶上这条线。:)是的,你一定是对的。当虚拟机可以确定地推断出这些引用永远不会被使用时,就不需要保持这些引用的活动状态。
根据closure的定义:
A"closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that"closes" the expression).
如果定义的函数使用的变量是在函数外部定义的,则使用closure。(我们称之为自由变量)。它们都使用closure(甚至在第一个示例中也是如此)。
- 第三个版本如何使用在函数外部定义的变量?
- @jon返回的函数使用外部定义的i2。
- @kev如果你定义了一个函数,它使用了一个在函数外部定义的变量……那么在"案例1:你朋友的程序"的"aadit m shah"中,答案是"函数f"一个闭包?它使用i(在函数外部定义的变量)。全球范围是否涉及限定因素?
- 下面是关于JavaScript闭包的详细易懂的文章
简而言之,JavaScript闭包允许函数访问在词法父函数中声明的变量。好的。
让我们看一个更详细的解释。要理解闭包,重要的是要了解JavaScript如何定义变量。好的。
作用域好的。
在javascript中,作用域是用函数定义的。每个函数都定义了一个新的作用域。好的。
考虑以下示例:好的。
1 2 3 4 5 6 7 8 9 10 11
| 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打印好的。
1 2 3 4
| hello
hello
2
Am I Accessible? |
现在我们来考虑一下,我们在另一个函数f中定义了一个函数g。好的。
1 2 3 4 5 6 7 8
| function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f |
我们把f称为g的词汇父代。如前所述,我们现在有两个范围:范围f和范围g。好的。
但是一个范围是"在"另一个范围内,那么子函数的范围是父函数范围的一部分吗?父函数范围内声明的变量会发生什么情况;我能否从子函数范围访问它们?这正是闭包介入的地方。好的。
闭包好的。
在javascript中,函数g不仅可以访问作用域g中声明的任何变量,还可以访问父函数f作用域中声明的任何变量。好的。
考虑以下事项:好的。
1 2 3 4 5 6 7 8 9 10 11
| 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打印好的。
让我们看一看console.log(foo);行。此时,我们在g范围内,我们试图访问在f范围内声明的变量foo。但如前所述,我们可以访问词汇父函数中声明的任何变量,这里就是这种情况;g是f的词汇父函数。因此打印了hello。现在我们来看看console.log(bar);行。此时,我们在f范围内,我们试图访问在g范围内声明的变量bar。bar未在当前范围内声明,函数g不是f的父级,因此bar未定义。好的。
实际上,我们还可以访问词汇"grand parent"函数范围内声明的变量。因此,如果在函数g中定义了一个函数h。好的。
1 2 3 4 5 6 7 8 9 10 11 12
| 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就可以访问函数h、g和f范围内声明的所有变量。这是用闭包完成的。在javascript闭包中,我们可以访问在词法父函数、词法父函数、词法父函数等中声明的任何变量。这可以看作是一个作用域链; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... ,直到最后一个没有词法父级的父函数为止。好的。
窗口对象好的。
实际上,链不会在最后一个父函数处停止。还有一个更特殊的范围:全局范围。未在函数中声明的每个变量都被认为是在全局范围中声明的。全球范围有两个特点;好的。
- 全局范围中声明的每个变量都可以在任何地方访问
- 全局范围中声明的变量对应于window对象的属性。
因此,在全局范围内声明变量foo有两种方法:要么不在函数中声明,要么设置窗口对象的属性foo。好的。
两次尝试都使用闭包好的。
既然您已经阅读了更详细的解释,那么现在很明显这两个解决方案都使用闭包。但要确定的是,让我们做一个证明。好的。
让我们创建一种新的编程语言;javascript没有闭包。顾名思义,javascript没有一个闭包与javascript相同,只是它不支持闭包。好的。
换言之;好的。
1 2 3 4 5
| var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello |
好吧,让我们看看第一个没有闭包的javascript解决方案会发生什么情况;好的。
1 2 3 4 5 6 7 8
| 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中打印10次undefined而不关闭。好的。
因此,第一个解决方案使用闭包。好的。
让我们看看第二个解决方案;好的。
1 2 3 4 5 6 7
| 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中打印10次undefined而不关闭。好的。
两种解决方案都使用闭包。好的。
编辑:假设这3个代码段没有在全局范围内定义。否则,变量foo和i将绑定到window对象,因此可以通过javascript和javascript中的window对象访问,而不会关闭。好的。好啊。
- 为什么i不定义?您只是引用父作用域,如果没有闭包,它仍然有效。
- 出于与foo相同的原因,在javascript中未定义任何闭包。由于javascript中允许访问词汇父级中定义的变量的功能,因此在javascript中没有定义i。这个特性称为闭包。
- 您不理解引用已经定义的变量和自由变量之间的区别。在闭包中,我们定义必须在外部上下文中绑定的自由变量。在代码中,您只需在定义函数时将i2设置为i。这使得i不是一个自由变量。尽管如此,我们仍然认为你的函数是一个闭包,但是没有任何自由变量,这就是重点。
- @利默斯:我认为brillout.com试图解释的是:如果函数只有它们自己的作用域(也就是说,没有作用域链),该怎么办?那么,i实际上就是undefined,因为它在执行外部函数时不可用(即使它仍然在for循环中执行)。他的观点是:没有闭包,使用高阶函数变得非常有限。
- @Abel是的,我知道了;但是我们可以讨论是否可以访问i,因为特性"闭包"或者JS中的"变量范围"是如何工作的,它通过文本父函数从当前函数到达全局范围。就像在C/C++/Java(或类似语言)中,我们可以访问当前块,父块…直到函数作用域,然后用OOP语言的类作用域,最后是一些全局作用域。只是在JS中我们没有块,但是我们有函数来限定变量名称空间的范围。
- @利默斯,我同意。与公认的答案相比,这并不能真正说明实际发生了什么。:)
- 我认为这是最好的答案,一般简单地解释闭包,然后进入特定的用例。谢谢!
我从来没有对任何人解释这件事的方式感到高兴。
理解闭包的关键是理解没有闭包的JS是什么样子的。
如果没有闭包,这将引发一个错误
1 2 3 4 5 6 7 8
| function outerFunc(){
var outerVar = 'an outerFunc var';
return function(){
alert(outerVar);
}
}
outerFunc()(); //returns inner function and fires it |
一旦outerfunc在一个虚构的禁用闭包的javascript版本中返回,对outervar的引用将被垃圾收集并消失,不会留下任何内容供内部func引用。
闭包本质上是一种特殊的规则,当内部函数引用外部函数的变量时,这些变量就可能存在。使用闭包时,即使在完成外部函数之后,也会维护引用的var,如果这样做有助于记住要点,则也可以使用"closed"。
即使使用闭包,在没有引用其局部函数的内部函数的函数中,局部变量的生命周期工作方式与在无闭包版本中相同。函数完成后,局部变量将被垃圾收集。
一旦在内部func中有一个对外部var的引用,但是它就像一个门柱被放置在这些引用var的垃圾收集方式中。
查看闭包的一个更准确的方法是,内部函数基本上使用内部范围作为自己的范围定义。
但是引用的上下文实际上是持久的,不像快照。重复地触发一个返回的内部函数,该函数不断递增,并记录外部函数的本地var,这将不断提醒更高的值。
1 2 3 4 5 6 7
| function outerFunc(){
var incrementMe = 0;
return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2 |
- 你说的"快照"(我想,你指的是我的答案)是对的。我在找一个能表明这种行为的词。在您的示例中,它可以看作是一个"hotlink"闭包结构。当捕获闭包作为内部函数中的参数时,可以将其声明为"快照"。但我同意,误用的词语只会增加主题的混乱。如果你对此有任何建议,我会更新我的答案。
- 如果您给内部函数一个命名函数,这可能有助于解释。
- 如果没有闭包,您将得到一个错误,因为您试图使用一个不存在的变量。
- 隐马尔可夫模型。。。好点。引用一个未定义的var是否从未引发过错误,因为它最终会在全局对象上查找为属性,或者我是否会混淆对未定义的var的赋值?
你们都在使用闭包。
我要用维基百科的定义:
In computer science, a closure (also lexical closure or function
closure) is a function or reference to a function together with a
referencing environment—a table storing a reference to each of the
non-local variables (also called free variables) of that function.
A closure—unlike a plain function pointer—allows a function to access
those non-local variables even when invoked outside of its immediate
lexical scope.
您朋友的尝试显然使用了变量i,它是非本地的,通过利用它的值并复制到本地i2中来存储。
您自己的尝试将i(调用站点在作用域中)作为参数传递给匿名函数。到目前为止,这不是一个闭包,但是该函数返回另一个引用同一i2的函数。由于内部匿名函数i2不是本地函数,这就创建了一个闭包。
- 是的,但我认为关键是他是怎么做到的。他只是把i复制到i2,然后定义一些逻辑并执行这个函数。如果我不立即执行它,而是将它存储在一个var中,并在循环之后执行它,它将打印10,不是吗?所以它没有捕捉到我。
- @利默斯:它抓到了埃多克斯一号[一号],很好。您描述的行为不是闭包与非闭包的结果,而是同时更改了闭包变量的结果。通过立即调用一个函数并将i作为一个参数(该参数将当前值当场复制)来使用不同的语法来执行相同的操作。如果你把自己的setTimeout放在另一个setTimeout里,同样的事情也会发生。
你和你的朋友都使用闭包:
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.
MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
在你朋友的代码函数function(){ console.log(i2); }中定义了匿名函数function(){ var i2 = i; ...的闭包内,可以读写局部变量i2。
在您的代码函数function(){ console.log(i2); }中,函数function(i2){ return ...在闭包中定义,并且可以读/写本地有价值的i2(在本例中声明为参数)。
在这两种情况下,函数function(){ console.log(i2); }然后传递到setTimeout。
另一个等价物(但内存利用率较低)是:
1 2 3 4 5 6 7 8
| function fGenerator(i2){
return function(){
console.log(i2);
}
}
for(var i = 0; i < 10; i++) {
setTimeout(fGenerator(i), 1000);
} |
- 我不明白为什么你的解决方案和我朋友的解决方案"更快,内存利用率更低",你能详细解释一下吗?
- 在解决方案中,创建20个函数对象(每个循环上有2个对象:2x10=20)。同样的结果就是你的朋友的答案。在"我的"解决方案中,只创建了11个函数对象:1个用于循环,10个"内部"-1+1x10=11。结果-内存使用减少,速度提高。
- 理论上,那是真的。在实践中,也可以看到这个jspef基准:jspef.com/closure-vs-name-function-in-a-loop/2
- @robw jspef.com/closures-and-for-loop
关闭
闭包不是函数,也不是表达式。它必须被视为一种"快照",来自函数作用域之外和函数内部使用的已用变量。从语法上讲,应该说:"取变量的闭包"。
换句话说,闭包是函数所依赖的变量的相关上下文的副本。
再一次(NA)?f):闭包可以访问不作为参数传递的变量。
记住,这些功能概念很大程度上取决于您使用的编程语言/环境。在JavaScript中,闭包依赖于词汇范围(在大多数C语言中都是这样)。
因此,返回一个函数主要是返回一个匿名/未命名的函数。当函数访问变量(不是作为参数传递)在其(词汇)范围内时,将执行一个闭包。
因此,关于你的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // 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)
} |
他们都在使用闭包。不要将执行点与闭包混淆。如果在错误的时刻获取闭包的"快照",则值可能是意外的,但肯定会获取闭包!
- 下面是关于JavaScript闭包的详细易懂的文章
让我们看看这两种方法:
1 2 3 4 5 6
| (function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})(); |
声明并立即执行一个匿名函数,该函数在自己的上下文中运行setTimeout()。i的当前值是通过首先将副本复制到i2中来保留的;它是由于立即执行而起作用的。
1 2 3 4 5
| setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000); |
声明内部函数的执行上下文,其中i的当前值保留到i2中;此方法还使用立即执行来保留该值。
重要的
应该指出,两种方法之间的运行语义不相同;您的内部函数被传递给setTimeout(),而其内部函数调用setTimeout()。
将这两个代码包装在另一个setTimeout()中并不能证明只有第二种方法使用闭包,只是开始时没有相同的事情。
结论
这两种方法都使用闭包,因此归根结底,这取决于个人的喜好;第二种方法更容易"移动"或概括。
- 我认为区别在于:他的解决方案(第一个)是通过引用来捕获,而我的解决方案(第二个)是通过价值来捕获。在这种情况下,没有什么区别,但是如果我将执行放在另一个设置中,我们会发现他的解决方案存在这样一个问题:它使用的是i的最终值,而不是当前值,而我的窗台使用的是当前值(因为它是由值捕获的)。
- @leemes您以相同的方式捕获;通过函数参数或赋值传递变量是相同的事情…你能补充一下你的问题吗?你将如何在另一个setTimeout()中结束执行?
- 让我看看这个……我想说明的是,函数对象可以被传递,原始变量i可以在不影响要打印的函数的情况下进行更改,而不取决于我们在哪里或何时执行它。
- 等等,您没有向(外部)设置超时传递函数。删除这些(),从而传递一个函数,您会看到输出10的10倍。
- @利默斯如前所述,()正是使他的代码工作的原因,就像你的(i)一样;你不仅包装了他的代码,还对它进行了更改。因此,您不能再进行有效的比较了。
- 嗯,我通过了i,但我返回了一个函数。所以我把一个函数传递给了setTimeout(它需要一个函数)。您也执行了函数,没有返回任何内容,但已经执行了最后的代码。因此,您没有将任何内容传递给setTimeout,因此在JS控制台中给了我一个错误。请注意,您的代码应该有2秒的延迟,但是它有1秒的延迟。因为外部超时是无用的。(这是我的控制台中的错误消息,实际上是一个警告。)
- @对了,他的代码会把undefined传递给setTimeout()的第一个参数;也就是说,两种方法之间的运行语义都不相同,这就是为什么你看到不同的行为。
- 因为我的被值捕获/绑定,他通过引用绑定,所以我们只看到在循环之后存储和执行结果闭包的区别。这可以使用setTimeout来完成,但也可以通过将其存储在一个函数数组中,然后执行它们来完成。这就是为什么我认为他的解决方案是不封闭的,但事实上它是封闭的;只是和我的解决方案不同,没有捕获变量。
- @利默斯,我想这是正确的说法:)
我刚才写这篇文章是为了提醒自己闭包是什么以及它在JS中的工作方式。
闭包是一个函数,当调用它时,它使用声明它的作用域,而不是调用它的作用域。在JavaScript中,所有函数的行为都是这样的。只要有一个函数仍然指向某个范围中的变量值,该范围中的变量值就会保持不变。规则的例外是"this",它指的是调用函数时该函数位于内部的对象。
1 2 3 4 5 6 7 8 9 10 11 12
| var z = 1;
function x(){
var z = 2;
y(function(){
alert(z);
});
}
function y(f){
var z = 3;
f();
}
x(); //alerts '2' |
仔细检查之后,看起来你们俩都在使用闭包。
在朋友的情况下,在匿名函数1中访问i,在匿名函数2中访问i2,其中console.log存在。
在您的例子中,您正在访问匿名函数内的i2,其中console.log存在。在console.log之前添加一条debugger;语句,在chrome开发者工具的"范围变量"下,它将告诉你变量在什么范围内。
- 右面板上的"闭包"部分被使用,因为没有更具体的名称。"局部"比"闭合"更能说明问题。
- 下面是关于JavaScript闭包的详细易懂的文章
考虑以下内容。这将创建并重新创建一个函数f,该函数在i上关闭,但不同!:
1 2 3 4 5 6 7 8 9 10 11
| i=100;
f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('
'));
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('
')); |
当以下内容关闭"a"函数本身时(他们自己!后面的代码段使用单个引用f
1 2 3
| for(var i = 0; i < 10; i++) {
setTimeout( new Function('console.log('+i+')'), 1000 );
} |
或者更明确地说:
1 2 3 4
| for(var i = 0; i < 10; i++) {
console.log( f = new Function( 'console.log('+i+')' ) );
setTimeout( f, 1000 );
} |
铌。f的最后一个定义是function(){ console.log(9) }在0打印之前。
警告!闭包概念可能是强制分散对基本编程本质的注意力:
1
| for(var i = 0; i < 10; i++) { setTimeout( 'console.log('+i+')', 1000 ); } |
X参考文献:JavaScript闭包是如何工作的?JavaScript闭包说明(JS)闭包是否需要函数内部的函数如何理解JavaScript中的闭包?javascript局部和全局变量混淆
- 第一次尝试的代码片段-不确定如何控制-Run' only was desired - not sure how to remove the 副本`
我想分享我的例子和一个关于闭包的解释。我做了一个Python示例,并用两个数字来演示堆栈状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def maker(a, b, n):
margin_top = 2
padding = 4
def message(msg):
print('
’ * margin_top, a * n,
' ‘ * padding, msg, ' ‘ * padding, b * n)
return message
f = maker('*', '#', 5)
g = maker('?', '?’, 3)
…
f('hello')
g(‘good bye!') |
此代码的输出如下:
1 2 3
| ***** hello #####
??? good bye! ??? |
这里有两个图显示堆栈和附加到函数对象的闭包。
当函数从maker返回时
稍后调用函数时
当通过参数或非局部变量调用函数时,代码需要局部变量绑定,如Margin_Top、Padding以及a、b、n。为了确保函数代码正常工作,很久以前就不存在的maker函数的堆栈框架应该是可访问的,它备份在闭包中,我们可以通过h函数消息对象。
- 我想删除这个答案。我意识到问题不在于什么是闭包,所以我想把它转移到另一个问题上。