简而言之, 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
不仅可以访问范围内声明的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
让我们看看线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
thenh
将能够访问在 function 、 和 . 范围内声明的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 相同,只是它不支持闭包。
换句话说;
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-No-Closure 中打印 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-No-Closure 中打印 10 次。
两种解决方案都使用闭包。
编辑:假设这 3 个代码片段未在全局范围内定义。否则,变量foo
和i
将绑定到window
对象,因此可以通过window
JavaScript 和 JavaScript-No-Closure 中的对象访问。