JavaScript关闭与匿名函数
我和我的一位朋友正在讨论什么是JS的封闭,什么不是。 我们只是想确保我们确实正确地理解它。
我们来看一下这个例子。 我们有一个计数循环,并希望在控制台上打印计数器变量。 因此,我们使用setTimeout
和闭包来捕获计数器变量的值,以确保它不会打印N次N值。
错误的解决方案,而闭合或接近闭合 ,以将任何东西:
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
,但他坚持认为他是。 我证明他没有使用闭包 ,将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,因此它们可以解决原始问题,但我们希望了解这两种解决方案中的哪一种使用闭包来实现此目的。
编者注: JavaScript中的所有函数都是闭包,如本文所述。 然而,我们只关心从理论角度确定这些功能的一个子集。 此后,除非另有说明,否则对闭包一词的任何引用都将指代这些功能子集。
关闭的简单解释:
现在让我们用这个来确定谁使用闭包,谁不使用(为了解释我已经命名了函数):
案例1:你的朋友的计划
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
关闭 。 因此函数g
是自由变量i2
(它是g
的上限)的一个闭包, 当它在setTimeout
内被引用 时 。
对你不好:你的朋友正在使用闭包。 内部函数是一个闭包。
案例2:您的计划
for (var i = 0; i < 10; i++) {
setTimeout((function f(i2) {
return function g() {
console.log(i2);
};
})(i), 1000);
}
在上面的程序中有两个功能: f
和g
。 让我们看看他们是否关闭:
对于f
:
i2
是一个局部变量。 g
是一个局部变量。 console
是一个自由变量。 console
绑定到全局范围。 console
没有被f
关闭 。 因此函数f
不是闭包。
对于g
:
console
是一个自由变量。 i2
是一个自由变量。 console
绑定到全局范围。 i2
受 f
。 setTimeout
的范围 。 console
没有被g
关闭 。 i2
被g
关闭 。 因此函数g
是自由变量i2
(它是g
的上限)的一个闭包, 当它在setTimeout
内被引用 时 。
对你有好处:你正在使用闭包。 内部函数是一个闭包。
所以你和你的朋友都在使用闭包。 不要争论。 我希望我能清除闭包的概念,以及如何为你们俩识别它们。
编辑:一个简单的解释,为什么所有的函数关闭(积分@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
定义:
“闭包”是一个表达式(通常是一个函数),可以将自由变量与一个绑定这些变量的环境 (即“关闭”表达式)一起使用。
如果你定义了一个函数,它使用了一个在函数之外定义的变量,那么你正在使用closure
。 (我们称该变量为一个自由变量 )。
他们都使用closure
(即使在第一个例子中)。
简而言之, Javascript Closures允许函数访问 在词法父函数中声明 的变量 。
让我们看看更详细的解释。 为了理解闭包,了解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
我们将调用f
的词汇父 g
。 如前所述,我们现在有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
调用f打印
hello
undefined
让我们看看console.log(foo);
这一行console.log(foo);
。 此时我们在范围g
并且我们试图访问在范围f
声明的变量foo
。 但是如前所述,我们可以访问任何在这里就是这样的词法父函数中声明的变量; g
是f
的词汇父母。 所以hello
打印。
现在我们来看一下console.log(bar);
。 此时我们在范围f
,我们试图访问在范围g
声明的变量bar
。 bar
在当前范围内未声明,并且函数g
不是f
的父级,因此bar
未定义
实际上,我们也可以访问在词汇“祖父”函数范围内声明的变量。 因此,如果将有功能h
函数内定义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
将能够访问在函数h
, g
和f
范围内声明的所有变量。 这是通过关闭来完成的。 在JavaScript中, 闭包允许我们访问在词汇父级函数,词汇盛大父级函数,词汇盛大父级函数等中声明的任何变量。这可以看作是范围链 ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
直到最后一个没有词汇父级的父函数。
窗口对象
事实上,链条并不停止在最后的父功能。 还有一个特殊的范围; 全球范围 。 未在函数中声明的每个变量都被认为是在全局范围内声明的。 全球范围有两个专业;
window
对象的属性。 因此,在全局范围内声明一个变量foo
方法有两种。 要么不通过函数声明,要么通过设置窗口对象的属性foo
。
这两种尝试都使用闭包
现在您已经阅读了更详细的解释,现在可以明显看出两种解决方案都使用闭包。 但是可以肯定的是,让我们来证明一下。
我们来创建一个新的编程语言; 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的第一个解决方案会发生什么;
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
在JavaScript的无盖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
在JavaScript的无盖10倍。
两种解决方案都使用闭包。
编辑:假定这3个代码片段没有在全局范围内定义。 否则,变量foo
和i
将绑定到window
对象,因此可以通过JavaScript和JavaScript-No-Closure中的window
对象访问。