一言以蔽之的Javascript闭包允许函数访问的变量是在词法父函数声明。
让我们看一个更详细的解释。要了解闭包,重要的是要了解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?
现在让我们考虑g
在另一个函数中定义一个函数的情况f
。
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
不仅可以访问在scope中声明的任何变量,g
而且还可以访问在父函数的scope中声明的任何变量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);
。至此,我们进入了作用域,g
并尝试访问foo
在作用域中声明的变量f
。但是如前所述,我们可以访问在词法父函数中声明的任何变量。g
是。的词汇父f
。因此hello
被打印。
现在让我们看一下这行console.log(bar);
。至此,我们进入了作用域f
,我们尝试访问bar
在作用域中声明的变量g
。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相同,只是不支持Closures。
换一种说法;
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
对象,并因此通过访问window
在JavaScript和JavaScript的无关闭对象。