函数的执行依赖于变量的作用域,这个变量的作用域是在函数定义的时候决定的,而不是在函数调用的时候决定的。为了实现这个词法作用域,javaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。
理解闭包先要理解嵌套函数的词法作用域规则。举个例子
//全局变量
var scope = "global scope";
function checkScope() {
//局部变量
var scope = "local scope";
//在作用域内返回scope
function _f() {return scope};
return f();
}
checkScope(); //结果local scope
分析:函数checkScope声明了一个局部变量,并定义了一个函数f(),函数f()返回了这个局部变量的值,最后将函数f()的执行结果返回。现在我们将这个函数稍微的改变下,看看返回的结果是什么?
//全局变量
var scope = "global scope";
function checkScope() {
//局部变量
var scope = "local scope";
//在作用域内返回scope
function _f() {return scope};
return f;
}
checkScope()(); //结果是什么呢
我们再在仔细分析下这个函数,和上个函数相比,变化就是返回的结果不是内部函数f()的运行结果,而是直接返回函数f()。在定义函数的作用域外调用这个函数会发生什么情况呢?
回想一下词法作用域的规则:JavaScript函数的执行用到的作用域链,这个作用域链是在定义函数的时候创建的。嵌套函数f()定义在这个作用链内,其中的变量scope是局部变量,不管在何时运行这个函数f(),这种绑定在执行时依然有效。因此最后一行的结果不是“global scop”,而是“local scope”。
闭包的这种特性,强大到令我们吃惊,它们可以捕捉到局部变量,并一并保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。
在同一个作用域链内定义两个闭包函数,这两个闭包函数共享私有变量或变量。但是要特别小心那些不希望共享的变量往往不经意间共享给 其他闭包函数。
function constfunc(value) {
return function() {return value}
};
//创建一个数组用来保存常量函数
var func[];
for (var i=0; i<10; i++) {
func[i] = constfunc(i);
};
//数组第5个位置上的函数返回的结果为5
func[5](); //5
上述代码利用循环创建了很多个闭包,当些这样的函数时往往会将循环体写在闭包函数之内。来看下下面的例子:
function constfunc() {
//创建一个数组用来保存常量函数
var func[];
for (var i=0; i<10; i++) {
func[i] = function() {return i;}
retrun func;
};
//数组第5个位置上的函数返回的结果又是什么呢?
func[5]();
分析下这个函数,这段代码创建了10个闭包,并将它们存储在一个数组中,这些闭包都是在同一个函数中定义的,共享变量i,当执行函数constfunc(),变量i的值是10,所有的闭包都共享这一个变量,因此数组中的函数返回的结果都是10。
嵌套函数不会将作用域链上的私有变量复制一份,也不会对所有绑定的变量生成静态快照。