我的一个朋友和我正在讨论什么是JS中的闭包,什么不是。我们只是想确保我们理解正确。
让我们举个例子。我们有一个计数循环,并希望在控制台上延迟打印计数器变量。因此,我们使用setTimeout和闭包来捕获计数器变量的值,以确保它不会打印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,因此它们解决了最初的问题,但我们想了解这两个解决方案中哪一个使用闭包来完成这一任务。
关闭
闭包不是函数,也不是表达式。它必须被视为一种从函数作用域外使用的变量到函数内部使用的“快照”。从语法上讲,我们应该说:“取变量的闭包”。
同样,换句话说:闭包是函数所依赖的变量的相关上下文的副本。
再说一次(naïf):闭包可以访问没有作为参数传递的变量。
请记住,这些函数概念在很大程度上取决于所使用的编程语言/环境。在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)
}
所有都使用闭包。不要将执行点与闭包混淆。如果闭包的“快照”是在错误的时刻拍摄的,值可能是意外的,但肯定是一个闭包!
你和你的朋友都使用闭包:
闭包是一种特殊的对象,它结合了两个东西:函数和创建该函数的环境。环境由创建闭包时范围内的任何局部变量组成。
MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
在你朋友的代码函数function(){console.log(i2);}定义在匿名函数闭包内的函数function(){var i2 = i;…可读写局部变量i2。
在代码中,函数function(){console.log(i2);}定义在函数闭包内的函数函数(i2){返回…并且可以读/写本地值i2(在本例中声明为参数)。
在这两种情况下,函数function(){console.log(i2);}然后传递到setTimeout。
另一个等价的(但内存占用较少)是:
function fGenerator(i2){
return function(){
console.log(i2);
}
}
for(var i = 0; i < 10; i++) {
setTimeout(fGenerator(i), 1000);
}
我想分享我的例子和关于闭包的解释。我做了一个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, padding以及a, b, n。为了确保函数代码正常工作,应该可以访问很久以前消失的maker函数的堆栈帧,它在我们可以找到的闭包中与函数消息对象一起备份。