简而言之,Javascript闭包允许函数访问在词法父函数中声明的变量。
为了理解闭包,重要的是要了解JavaScript如何作用域变量。
作用域
在JavaScript中,通过函数定义作用域。每个函数定义一个新的作用域。
考虑以下示例:
function f()
{
var foo='hello';
for(var i=0;i<2;i++){
var bar = 'Am I accessible?';
console.log(foo);
}
console.log(i);
console.log(bar);
}
调用 f 输出
hello
hello
2
Am I Accessible?
现在考虑这样一种情况,我们有一个在另一个函数f
中定义的函数g
。
function f()
{
function g()
{
}
}
我们将称
f
为
g
的
词法父级。
如前所述,我们现在有两个作用域;作用域
f
和作用域
g
。
但是一个作用域“包含”另一个作用域,那么子函数的作用域是否属于父函数的作用域?在父函数的作用域中声明的变量会发生什么?我能否从子函数的作用域中访问它们?
这正是闭包介入的地方。
闭包
在JavaScript中,函数
g
不仅可以访问在作用域
g
中声明的任何变量,还可以访问在父函数
f
的作用域中声明的任何变量。
考虑以下内容;
function f()
{
var foo='hello';
function g()
{
var bar='bla';
console.log(foo);
}
g();
console.log(bar);
}
调用 f 会输出
hello
undefined
让我们看看代码行console.log(foo);
。此时我们处于作用域g
,并尝试访问在作用域f
中声明的变量foo
。但正如之前所述,我们可以访问任何在词法父函数中声明的变量,这在这里是成立的;g
是f
的词法父级。因此打印出了hello
。
现在让我们看看代码行console.log(bar);
。此时我们处于作用域f
,并尝试访问在作用域g
中声明的变量bar
。bar
没有在当前作用域中声明,且函数g
不是f
的父级,因此bar
为未定义。
实际上,我们还可以访问词法“曾祖父”函数的作用域中声明的变量。因此,如果在函数g
中定义了函数h
,我们也可以访问在函数h
中声明的变量。
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中,
闭包允许我们访问在词法父函数中声明的任何变量,以及在词法祖父函数、词法曾祖父函数等中声明的变量。
这可以看作是一个
作用域链;
当前函数的作用域->词法父函数的作用域->词法祖父函数的作用域->...
,一直到没有词法父级的最后一个父函数。
window对象
实际上,链并不会在最后一个父函数停止。还有一个特殊的作用域;
全局作用域。在函数中未声明的每个变量都被认为是在全局作用域中声明的。全局作用域有两个特点:
- 在全局作用域中声明的每个变量都可以随处访问
- 在全局作用域中声明的变量对应于
window
对象的属性。
因此,在全局作用域中声明变量
foo
有两种方法;要么不在函数中声明它,要么设置
window
对象的属性
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);
}, 1000)
})();
}
因此,在JavaScript-No-Closure中,这将打印十次
undefined
。因此第一个解决方案使用闭包。现在让我们看看第二个解决方案;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
因此,这将在JavaScript-No-Closure中打印十次
undefined
。两种解决方案都使用闭包。编辑:假设这3个代码片段未在全局范围内定义。否则,变量
foo
和
i
将绑定到
window
对象,并因此在JavaScript和JavaScript-No-Closure中通过
window
对象访问。