javascript - Javascript闭包VS 匿名函数

  显示原文与译文双语对照的内容

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

我们来看看这个例子。我们有一个计数循环,并且想要打印控制台上的计数器变量延迟。 因此我们使用setTimeout和闭包来捕获计数器变量的值,以确保它不会打印N 次的值N 。

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


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

在循环完成后,即打印 10这将当然次的值

他的尝试是:


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

按预期打印 0到 9.

我告诉他,他没有使用一个 闭包的方式捕获 i,但他坚持他会。 我证明他不使用闭包来在另一个 setTimeout ( 将匿名函数传递给 setTimeout ) 中放入loop循环 body,再打印 10次 10. 如果我将他的功能在各种复杂环境和存储而言也一样的循环,同时,印刷 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延迟,所以他们解决原来的问题,但我们想了解其中的哪一个两个解决方案使用闭包来做到这一点。

时间:

根据 closure 定义:

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

如果定义了一个函数,该函数使用了一个函数,该函数在函数之外定义了一个变量。 ( 我们将变量称为免费变量 ) 。
他们都使用 closure ( 即使在 1的示例中) 。

在一个, lexical-parent函数简而言之 Javascript闭包允许一个函数来访问一个变量 declared.

让我们看看更详细的解释。 要理解闭包,理解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

调用打印


hello
hello
2
Am I Accessible?

现在我们来考虑一下,在另一个函数中定义了一个函数 g


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

我们将调用父的gf的词法。 如上所述,我们现在有 2个作用域;作用域 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

调用打印

 
hello
undefined

 

让我们看看行 console.log(foo); 。 此时我们在作用域中声明 g 和我们试图访问该变量 foof 范围。 但是像刚才提到我们可以访问父函数这里就是这种情况;g 中声明的任何变量的词法是词法 f的父项。 因此打印 hello
现在让我们看看 console.log(bar); 行。 此时我们在作用域中声明 f 和我们试图访问该变量 barg 范围。 bar 不能在当前作用域中声明,并且函数 g 不是 f的父级,因此 bar 未定义

实际上我们也可以访问在词法"grand"函数范围内声明的变量。 因此如果将有一个函数定义在函数内 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

然后 h 可以访问在函数 hgf 范围内声明的所有变量。 这是用的闭包完成的。 在 JavaScript 闭包使我们能够访问父函数,在词法中声明的任何变量的词法父函数,在词法调度员grand-grand父函数,等等 既可以将它的视为一个作用域链scope of current function -> scope of lexical parent function -> scope of lexical grand parent function ->.. . 直到最后一个没有词汇父级的父函数。

在窗口对象

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

  • 在全局范围中声明是可以到处, 访问每一个变量
  • 全局作用域中声明的变量对应于 window 对象的属性。

因此,在全局作用域中声明变量 foo的方法正好有两种;要么不在函数中声明它,要么通过设置窗口对象的属性 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)
 })();
}

因此在这会打印出 undefined 10程式修改

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

让我们看看第二个解决方案;


for(var i = 0; i <10; i++) {
 setTimeout((function(i2){
 return function() {
 console.log(i2);//i2 is undefined in JavaScript-No-Closure
 }
 })(i), 1000);
}

因此在这会打印出 undefined 10程式修改

两个解决方案都使用闭包。

编辑:假定在全局作用域中没有定义这 3个代码段。 在两个JavaScript和 windowfooi 否则变量会被绑定到的对象,因此可以使用备份 window object.

你都在使用闭包。

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

在计算机科学中,闭包( 词汇关闭或者函数关闭) 是一个函数或者对函数的引用,以及一个引用environment—a表的引用,它存储了该函数的每个non-local变量( 也称为免费变量)的引用。 closure—unlike一个普通函数pointer—allows一个函数,用来访问那些non-local变量,即使在它的即时词法范围之外调用。

你对朋友的尝试显然使用了变量 i,它是 non-local,通过获取值并将一个副本存储到本地 i2 中。

你自己的尝试将 i ( 调用站点的作用域) 作为参数传递给一个匿名函数。 这还不是一个闭包,但是那个函数返回另一个引用相同 i2的函数。 由于内部匿名函数 i2 不是局部的,这就创建了一个闭包。

我从来都不喜欢别人对此的解释。

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

没有闭包的,这将提醒'未定义'


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

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

一旦outerFunc在一个虚构的closure-disabled版本的JavaScript中返回,对outerVar的引用就会被垃圾收集,并且在里面没有任何东西可以引用内部函数。

闭包本质上是一个特殊规则,当内部函数引用函数的外部变量时,它可以使这些var存在。 有了闭包,引用的var即使在外部函数完成后也被维护,或者'已经关闭'如果能帮助你记住这点。

即使有闭包,函数在一个没有内部函数的函数中的局部变量的生命周期也与在closure-less版本中相同。 函数完成后,局部变量会得到垃圾收集。

在内部函数中对外部变量有了引用之后,就像doorjamb一样,它就像那些引用的var中的垃圾回收一样被放入垃圾回收。

查看闭包的一个更精确的方法是,内部函数基本上使用内部作用域作为它自己的作用域 foudnation 。

,但引用的上下文实际上是persistent持久的,不像快照。 重复激发一个返回的内部函数,它不断地增加和记录函数var的外部局部,这将保持警告更高的值。


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

结算

闭包不是函数,也不是表达式。 它必须被看作是一个'快照',它来自于functionscope之外的变量并在函数内部使用。 语法上,应该说:'关闭变量的闭包'。

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

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

因此,返回函数主要返回匿名/未命名函数。 当函数访问变量没有作为参数传递,并且在它的( 词汇) 作用域内时,就会得到一个闭包。

关于你的例子:


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

都使用闭包。不要将执行点与闭包混淆。 如果闭包的'快照'是在错误的时刻获取的,这些值可能是意外的,但肯定是一个闭包 !

让我们看看两种方法:


(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() 还没有证明只有第二种方法使用闭包,有只是不是同样的事情以 begin.

结论

这两种方法都使用闭包,所以它要归结于个人品味;第二种方法是容易或者泛化成周围"移库"。

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

在你的朋友案例中,i 是在匿名函数 1中访问的,而 i2 是在匿名函数 2中访问的,该函数是 console.log

在你的情况下,你正在使用匿名函数访问 i2 。 添加一个 debugger; 语句之前 console.log 和 Chrome 开发工具"作用域变量"下它会告诉在什么范围中的变量是。

我曾经写过这篇文章来提醒自己闭包是什么以及它在JS中的工作方式。

闭包是一个函数,在调用时使用它声明的作用域,而不是调用它的作用域。 在javaScript中,所有函数的行为如下。 变量中的变量值保持不变,只要有一个仍然指向它们的函数。 规则的例外是'这个',它引用函数在调用时所在的对象。


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

...