个人觉得理解闭包,首先要理解以下几个概念。
1、函数的作用域和作用域链
js不像java等其他类语言,它并不存在块级作用域,取而代之的是函数作用域,另一个变量作用域是全局作用域。
函数的作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。(摘自犀牛书P57)
在这里应该注意两点:一是函数作用域中声明提前的特性,见代码:
var scope="global"
function f(){
console.log(scope);//输出结果"undefined"
var scope="local";
console.log(scope);//输出结果"local"
}
function f(){
var scope; //变量声明提前
console.log(scope);//输出结果"undefined"
scope="local";//赋值留在原地
console.log(scope);//输出结果"local"
}
需要注意的第二点就是:在函数作用域内不用var声明变量,而是直接赋值给一个变量,那么该变量就会成为全局变量。但还不仅仅是这样,千万不要把该全局变量完全等同于在全局环境中用var声明的全局变量,两者还是有一定区别的。话不多说,上代码:
var turevar=1//生命同一个不可删除的全局变量
function fun(){
fakevar=2//在函数作用域内声明一个全局变量
}
this.fakevar1=3//同上
delete truevar//=>false 变量并没有被删除
delete fakevar//=>true 变量被删除
delete fakevar1//=>true 变量被删除
2、js中的执行环境和ECMAscript的执行流机制
执行环境:
定义了变量和函数有权访问的其他数据,决定了他们各自的行为。PS:每个执行环境都有一个与之相连的变量对象,如果这个环境是函数,则将其活动对象作为变量对象。
ECMAscript的执行流机制:
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出(该函数的执行环境销毁,保存在器重的所有变量核函数定义也随之销毁)——摘自:《JavaScript高级程序设计》P73
为了理解这两个概念看如下的代码:
var color="blue";
function changeColor(){
var anotherColor="red";
function swapColor(){
var tempColor=anotherColor;
anotherColor=color;
color=tempColor;
}
}
以上代码涉及三个执行环境:全局环境、changeColor()的局部环境和swapColors()的局部环境,如图所示:
该函数的作用域链图如上图所示,其中的矩形(卧槽好难看!)表示特定的执行环境。内部环境可以通过作用链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境直接的联系是线性,有次序的,每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。
最后要补充的一点是:JavaScript采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
在充分理解了以上几个概念之后,那么对于js中闭包的理解也就没那么困难了。
首先,闭包的概念:
函数对象可以通过作用域链相互关联起来,函数体内的变量可以保存在函数作用域内,这种特性被称为闭包。
为了更直观的了解闭包,先上一段代码:
var scope='global scope';//全局变量
function checkscope(){
var scope="local scope";//局部变量
function f(){return scope;}//在作用域中返回这个值
return f();
}
checkscope()//=>输出结果local scope
理解以上代码并不难,现在对这段代码做一点的改动
var scope='global scope';
function checkscope(){
var scope="local scope";
function f(){return scope;}
return f;
}
checkscope()();
我们知道调用函数checkscope(),我们得到的不再是一个scope值,而是一个函数f;再接着调用函数f(),这里就出现了一个问题,调用函数f是在全局环境中调用,其中的scope值从何而来?上面提到过一个概念:js是采用的词法作用域,该scope值还是来自于该函数即f定义时的作用域,而不是调用时的作用域。所以结果还是local scope。
此外,在犀牛书上还遇见一个例子比较经典。见代码:
function constfuncs(){
var funcs=[];
for(var i=0;i<10;i++){
funcs[i]=function{ return i ;}
}
return funcs;
}
var funcs=constfuncs();
funcs[5]()
结合上面的概念一步一步分析:
首先,constfuncs()函数执行以后得到的是funcs数组,数组中是十个未执行的函数function(){return i}。当调用funcs[5]()的时候,变量i的值应该是函数在定义的时候i的取值,又因为直到i=10的时候循环才停止,所以,最后i的取值是10.函数调用返回值也是10。
从这里不能看出:关联到闭包的作用域链是“活动的”。嵌套函数不会将作用域内的私有成员复制一份,也不会对所绑定的变量生成静态快照。