JavaScriptclosures与匿名函数

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

我们来看一下这个例子。 我们有一个计数循环,并希望在控制台上延迟打印计数器variables。 因此,我们使用setTimeoutclosures来捕获计数器variables的值,以确保它不会打印N倍N值。

没有closures或任何closuresclosures的错误解决scheme将是:

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

这当然会打印10次循环后的i值,即10。

所以他的尝试是:

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

按预期打印0到9。

我告诉他,他没有用封口来抓i ,但他坚持说他是。 我certificate了他没有使用闭包 ,把for循环体放入另一个setTimeout (将匿名函数传给setTimeout ),再次打印10次。 如果我将它的函数存储在一个var并在循环之后执行,同样也适用于10次打印。10因此,我的观点是他并没有真正捕捉i的值 ,使得他的版本不是闭包。

我的尝试是:

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

所以我捕捉i (在closures中命名i2 ),但现在我返回另一个函数,并通过这个。 在我的情况下,传递给setTimeout的函数确实捕获了i

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

请注意,这两个解决scheme都在控制台上打印0到9,所以他们解决了原来的问题,但是我们想知道这两个解决scheme中哪一个使用闭包来完成这个任务。

编者注: JavaScript中的所有函数都是闭包,如本文所述。 然而,我们只关心从理论的angular度来确定这些function的一个子集。 此后,除非另有说明,对闭包这个词的任何引用都是指这个函数的子集。

封闭的一个简单的解释:

  1. 采取function。 我们称之为F.
  2. 列出F的所有variables
  3. variables可以有两种types:
    1. 局部variables(绑定variables)
    2. 非局部variables(自由variables)
  4. 如果F没有自由variables,那么它不能是一个闭包。
  5. 如果F有任何自由variables(在F 父范围中定义),则:
    1. F必须只有一个F的父范围被绑定到一个自由variables。
    2. 如果F是从父范围外部引用的 ,则它将成为自由variables的闭包。
    3. 这个自由variables被称为封闭F的upvalue。

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

案例1:你的朋友的计划

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

在上面的程序中有两个函数: fg 。 我们来看看它们是否是封闭的:

对于f

  1. 列出variables:
    1. i2是一个局部variables。
    2. i是一个自由variables。
    3. setTimeout是一个自由variables。
    4. g是一个局部variables。
    5. console是一个自由variables。
  2. find每个自由variables绑定到的父范围:
    1. i 必须在全球范围内。
    2. setTimeout 绑定到全局范围。
    3. console 绑定到全局范围。
  3. 在哪个范围内引用函数? 全球范围
    1. 所以i没有被f 封闭
    2. 因此setTimeout没有被f closures
    3. 因此, console没有被f closures

因此函数f不是闭包。

对于g

  1. 列出variables:
    1. console是一个自由variables。
    2. i2是一个自由variables。
  2. find每个自由variables绑定到的父范围:
    1. console 绑定到全局范围。
    2. i2绑定f的范围。
  3. 在哪个范围内引用函数? setTimeout范围
    1. 因此console没有被g closures
    2. 因此i2g closures

因此函数g是自由variablesi2 (它是g的上位)在setTimeout引用 的闭包。

对你不好:你的朋友正在使用closures。 内部函数是一个闭包。

案例2:您的计划

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

在上面的程序中有两个函数: fg 。 我们来看看它们是否是封闭的:

对于f

  1. 列出variables:
    1. i2是一个局部variables。
    2. g是一个局部variables。
    3. console是一个自由variables。
  2. find每个自由variables绑定到的父范围:
    1. console 绑定到全局范围。
  3. 在哪个范围内引用函数? 全球范围
    1. 因此, console没有被f closures

因此函数f不是闭包。

对于g

  1. 列出variables:
    1. console是一个自由variables。
    2. i2是一个自由variables。
  2. find每个自由variables绑定到的父范围:
    1. console 绑定到全局范围。
    2. i2绑定f的范围。
  3. 在哪个范围内引用函数? setTimeout范围
    1. 因此console没有被g closures
    2. 因此i2g closures

因此函数g是自由variablesi2 (它是g的上位)在setTimeout引用 的闭包。

对你有好处:你正在使用闭包。 内部函数是一个闭包。

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

编辑:一个简单的解释,为什么所有的函数closures(积分@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")); } } 

根据closure定义:

“闭包”是一个expression式(通常是一个函数),它可以将自由variables与一个绑定这些variables的环境 (即“closures”expression式)一起使用。

如果您定义的函数使用了在函数之外定义的variables,那么您正在使用closure 。 (我们称这个variables为一个自由variables )。
他们都使用closure (即使在第一个例子中)。

简而言之, Javascript Closures允许函数访问 在词汇父级函数中声明 的variables

让我们看看更详细的解释。 要理解闭包,理解JavaScript如何variables是很重要的。

领域

在JavaScript范围内定义的function。 每个函数都定义一个新的范围。

考虑下面的例子;

 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定义的函数f

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

我们将称之为f词汇父母 。 如前所述,我们现在有2个范围; 范围f和范围g

但是一个范围是在另一个范围内的“范围内”,父function范围的子范围部分的范围也是如此? 在父函数范围内声明的variables会发生什么情况; 我将能够从子function的范围访问它们吗? 这正是closures的地方。

closures

在JavaScript中,函数g不仅可以访问在范围g声明的任何variables,还可以访问在父函数f的范围内声明的任何variables。

考虑以下;

 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);这行console.log(foo); 。 在这一点上,我们在范围g ,我们试图访问在范围f声明的variablesfoo 。 但是如前所述,我们可以访问在这里是一个词法父函数声明的任何variables; gf的词汇父母。 所以hello打印。
现在我们来看一下console.log(bar); 。 在这一点上,我们在范围f ,我们尝试访问在范围g声明的variablesbarbar不在当前范围内声明,函数g不是f的父级,因此bar是未定义的

实际上,我们也可以访问在词汇“祖父”函数范围内声明的variables。 因此,如果在函数g定义了函数g

 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范围内声明的所有variables。 这是closures完成的。 在JavaScript中, 闭包允许我们访问任何在词法父函数,词法macros父函数,词法macros大父函数等中声明的variables。这可以看作是作用域链 ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...直到最后一个没有词法父项的父函数。

窗口对象

事实上,链不停在最后的父母function。 还有一个特殊的范围; 全球范围 。 未在函数中声明的每个variables都被认为是在全局范围内声明的。 全球范围有两个专业;

  • 在全局范围内声明的每个variables都可以访问
  • 全局范围中声明的variables对应于window对象的属性。

因此,在全局范围内有两种方法来声明variablesfoo 。 要么不通过函数声明,要么设置窗口对象的属性foo

这两种尝试都使用闭包

现在您已经阅读了更详细的解释,现在可以明显看出,两种解决scheme都使用闭包。 但是可以肯定的是,让我们来certificate一下。

让我们创build一个新的编程语言; JavaScript的无闭幕。 顾名思义,除了不支持闭包外,JavaScript-No-Closure与JavaScript完全相同。

换一种说法;

 var foo = 'hello'; function f(){console.log(foo)}; f(); //JavaScript-No-Closure prints undefined //JavaSript prints hello 

好的,让我们来看看JavaScript-No-Closure的第一个解决scheme会发生什么。

 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 10次​​。

因此第一个解决scheme使用闭包。

我们来看第二个解决scheme。

 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 10次​​。

两种解决scheme都使用闭包。

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

我从来没有任何人解释这个方式感到高兴。

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

没有closures,这会抛出一个错误

 function outerFunc(){ var outerVar = 'an outerFunc var'; return function(){ alert(outerVar); } } outerFunc()(); //returns inner function and fires it 

一旦outerFunc在一个假想的closures禁用版本的JavaScript中返回,对outerVar的引用将被垃圾收集,并没有留下任何内容来引用内部函数。

闭包本质上是特殊的规则,当内部函数引用一个外部函数的variables时,这些variables可以存在。 在closures的情况下,即使外部function完成,所引用的variables也会保持不变,如果这样可以帮助您记住这个点,则可以“closures”variables。

即使封闭,本地variables的生命周期中没有引用它的本地函数的内部函数的工作原理与无封闭版本中的相同。 function完成后,当地人会收集垃圾。

一旦你在一个内部函数中引用了一个外部variables,但是它就像是一个门框被这些引用variables的垃圾回收方式所取代。

内部函数基本上使用内部范围作为自己的范围函数,也许更准确的方法来看待闭包。

但是引用的上下文实际上是持久的,不像快照。 反复启动一个返回的内部函数,不断增加并logging一个外部函数的本地variables将保持提醒更高的值。

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

你们都在使用闭包。

我将在这里与维基百科的定义 :

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

你的朋友的尝试明确地使用variablesi ,这是非本地的,通过它的价值和复制存储到本地i2

你自己的尝试通过i (在呼叫站点范围内)作为一个参数的匿名函数。 到目前为止,这不是一个闭包,但是那个函数返回了引用相同i2另一个函数。 由于内部匿名函数i2不是本地的,所以这将创build一个闭包。

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

封闭是一种特殊的对象,它结合了两个方面:一个function和创buildfunction的环境。 环境由创build闭包时在范围内的任何局部variables组成。

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

在你朋友的代码函数function(){ console.log(i2); } function(){ console.log(i2); }在匿名函数function(){ var i2 = i; ... function(){ var i2 = i; ...并且可以读取/写入局部variablesi2

在你的代码函数function(){ console.log(i2); } function(){ console.log(i2); }在函数function(i2){ return ...并且可以读/写本地有价值的i2 (在这种情况下声明为参数)。

在这两种情况下函数function(){ console.log(i2); } 然后传入setTimeout

另一个等效(但具有较less的内存利用率)是:

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

closures

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

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

再次(naïf):闭包有权访问不作为parameter passing的variables。

请记住,这些function概念强烈依赖于您使用的编程语言/环境。 在JavaScript中,闭包取决于词法范围(在大多数c语言中都是如此)。

所以,返回一个函数大多是返回一个匿名/未命名的函数。 当函数访问variables不作为parameter passing时,并且在其((词法)范围内)已经closures了。

所以,关于你的例子:

 // 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) } 

所有正在使用闭包。 不要将执行点与closures混淆。 如果closures的“快照”是在错误的时刻进行的,则这些值可能是意想不到的,但肯定会closures!

我们来看看两种方式:

 (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()并不能certificate只有第二种方法使用闭包,但是开头的东西并不相同。

结论

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

我前一段时间写了这个提醒自己,封闭是什么,以及它如何在JS中工作。

闭包是一个被调用的函数,它使用它所声明的范围,而不是被调用的范围。 在javaScript中,所有的函数都像这样。 只要有一个仍然指向它们的函数,范围内的variables值就会一直存在。 规则的例外是“this”,它指的是函数在被调用时所在的对象。

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

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

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

在你的情况下,你正在访问内存console.log匿名函数的i2 。 添加一个debugger; console.log之前的语句以及“范围variables”下的chrome开发人员工具,它会告诉variables的范围。

考虑以下几点。 这创build并重新创build了一个closuresi的function,但不同的function!:

 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'));