简而言之,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的父元素。
如前所述,我们现在有两个范围;作用域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
Let's look at the line console.log(foo);. At this point we are in scope g and we try to access the variable foo that is declared in scope f. But as stated before we can access any variable declared in a lexical parent function which is the case here; g is the lexical parent of f. Therefore hello is printed.
Let's now look at the line console.log(bar);. At this point we are in scope f and we try to access the variable bar that is declared in scope g. bar is not declared in the current scope and the function g is not the parent of f, therefore bar is undefined
实际上,我们还可以访问词法“grandparent”函数作用域中声明的变量。因此,如果在函数g中定义了一个函数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
then h would be able to access all the variables declared in the scope of function h, g, and f. This is done with closures. In JavaScript closures allows us to access any variable declared in the lexical parent function, in the lexical grand parent function, in the lexical grand-grand parent function, etc.
This can be seen as a scope chain; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... until the last parent function that has no lexical parent.
窗口对象
实际上,这个链并不止于最后一个父函数。还有一个更特殊的范围;全球范围。每个没有在函数中声明的变量都被认为是在全局作用域中声明的。全球范围有两个专业;
在全局作用域中声明的每个变量都可以在任何地方访问
在全局作用域中声明的变量对应于窗口对象的属性。
因此,在全局作用域中声明变量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); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
因此,这将在javascript中打印10次undefined - no - closure。
因此,第一个解决方案使用闭包。
让我们看看第二个解;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
因此,这将在javascript中打印10次undefined - no - closure。
两种解决方案都使用闭包。
编辑:假设这3个代码片段没有在全局作用域中定义。否则变量foo和i将被绑定到窗口对象,因此在JavaScript和JavaScript- no - closure中都可以通过窗口对象访问。