一言以蔽之的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
然后h
将能够访问所有的功能范围内声明的变量h
,g
和f
。这是通过闭包完成的。在 JavaScript 中,闭包允许我们访问在词法父函数、词法祖父函数、词法祖父函数等中声明的任何变量。这可以看作是一个作用域链; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
直到最后一个没有词法父级的父级函数。
窗口对象
实际上,该链并没有在最后一个父函数处停止。还有一种特殊的作用域;在全球范围内。每个未在函数中声明的变量都被认为是在全局范围内声明的。全球范围有两个特点;
- 在全局范围内声明的每个变量都可以在任何地方访问
- 在全局作用域中声明的变量对应于
window
对象的属性。
因此foo
,在全局范围内声明变量的方法有两种:通过不在函数中声明它或通过设置foo
window 对象的属性。
两种尝试都使用闭包
现在您已经阅读了更详细的解释,现在很明显这两种解决方案都使用了闭包。但可以肯定的是,让我们做一个证明。
让我们创建一个新的编程语言;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 中的对象访问。