编者注:如本文所述,JavaScript中的所有函数都是闭包。然而,我们只对从理论角度来看有趣的这些函数的子集感兴趣。从今以后,除非另有说明,任何对闭包一词的引用都是指这个函数子集。
闭包的一个简单解释:
Take a function. Let's call it F.
List all the variables of F.
The variables may be of two types:
Local variables (bound variables)
Non-local variables (free variables)
If F has no free variables then it cannot be a closure.
If F has any free variables (which are defined in a parent scope of F) then:
There must be only one parent scope of F to which a free variable is bound.
If F is referenced from outside that parent scope, then it becomes a closure for that free variable.
That free variable is called an upvalue of the closure F.
现在让我们用这个来弄清楚谁使用闭包,谁不使用(为了解释,我已经命名了函数):
案例1:你朋友的程序
for (var i = 0; i < 10; i++) {
(function f() {
var i2 = i;
setTimeout(function g() {
console.log(i2);
}, 1000);
})();
}
在上面的程序中有两个函数:f和g。让我们看看它们是否是闭包:
f:
List the variables:
i2 is a local variable.
i is a free variable.
setTimeout is a free variable.
g is a local variable.
console is a free variable.
Find the parent scope to which each free variable is bound:
i is bound to the global scope.
setTimeout is bound to the global scope.
console is bound to the global scope.
In which scope is the function referenced? The global scope.
Hence i is not closed over by f.
Hence setTimeout is not closed over by f.
Hence console is not closed over by f.
因此函数f不是一个闭包。
g:
列出变量:
控制台是一个自由变量。
I2是自由变量。
找到每个自由变量所绑定的父作用域:
控制台绑定到全局作用域。
I2限定在f的作用域内。
函数在哪个范围内被引用?setTimeout的范围。
因此控制台不会被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是一个局部变量。
控制台是一个自由变量。
找到每个自由变量所绑定的父作用域:
控制台绑定到全局作用域。
函数在哪个范围内被引用?全球范围。
因此控制台不会被f关闭。
因此函数f不是一个闭包。
g:
列出变量:
控制台是一个自由变量。
I2是自由变量。
找到每个自由变量所绑定的父作用域:
控制台绑定到全局作用域。
I2限定在f的作用域内。
函数在哪个范围内被引用?setTimeout的范围。
因此控制台不会被g关闭。
因此i2除以g。
因此,函数g是自由变量i2(它是g的上值)的闭包,当它从setTimeout中引用时。
对你有好处:你正在使用闭包。内部函数是一个闭包。
您和您的朋友都在使用闭包。停止争论。我希望我清楚了闭包的概念以及如何为你们俩识别它们。
编辑:关于为什么所有函数都是闭包的简单解释(credit @Peter):
首先让我们考虑以下程序(它是控件):
lexicalScope ();
函数lexicalScope() {
var message = "这是控件。你应该能看到这条消息被提醒了。”
regularFunction ();
函数正则函数(){
alert (eval(“信息”));
}
}
我们知道lexicalScope和regularFunction都不是上述定义中的闭包。
当我们执行程序时,我们期望消息被警告,因为regularFunction不是一个闭包(即它可以访问父作用域中的所有变量——包括消息)。
当我们执行程序时,我们观察到消息确实被警告了。
接下来让我们考虑以下程序(它是替代方案):
var closureFunction = lexicalScope();
closureFunction ();
函数lexicalScope() {
var message = "这是另一种选择。如果你看到这个消息被警告,那么就意味着JavaScript中的每个函数都是一个闭包。”
返回函数closureFunction() {
alert (eval(“信息”));
};
}
从上面的定义中,我们知道只有closureFunction是闭包。
当我们执行程序时,我们希望message不会被警告,因为closureFunction是一个闭包(也就是说,在函数创建时,它只能访问它的所有非局部变量(见这个答案)-这不包括message)。
当我们执行程序时,我们观察到消息实际上正在被警告。
我们能从中推断出什么?
JavaScript解释器对待闭包的方式与对待其他函数的方式没有区别。
每个函数都带有它的作用域链。闭包没有单独的引用环境。
闭包就像其他函数一样。当它们在它们所属的作用域之外的作用域中被引用时,我们只称它们为闭包,因为这是一个有趣的情况。