先从一个例子入手:
function fun(){
var count = 0;
return function(){
return ++count;
}
}
var func = fun();
alert(func()); // 1
alert(func()); // 2
简单理解:闭包是一种具有状态的函数,或者将闭包的特征理解为,其相关的局部变量在函数调用结束后将会继续存在。从上面的例子中可以看出来,函数内的局部变量count在函数fun()调用之后依然有效。
步步深入闭包:
- 嵌套的函数声明
闭包的前提条件是需要在函数声明的内部声明另一个函数(即嵌套的函数声明)就像下面这样:
function foo(){
function bar(){
alert("bar is called !");
}
bar();
}
foo(); // bar is clalled !
在函数foo()中包含函数bar()的声明以及调用语句,在调用foo()函数时,就间接地调用了函数bar()。我们可以这么理解,变量foo是全局对象的属性,而bar是foo对象的一个属性。也没有太多不好理解的地方。
- 嵌套函数与作用域
对上面的代码作少许修改,像下面这样:
function foo(){
var number = 123;
function bar(){
alert(number);
alert("bar is called !");
}
bar();
}
foo();
// 123
// bar is clalled !
代码很直观,这里主要说说作用域,即在内层进行声明的函数bar(),可以访问外层的函数foo()的局部变量number,再往深的探讨:
之所以可以访问外部的变量,是因为内部函数的作用域链中包含了外部函数的作用域,这一句先放在这里,回过头来再看这一句
几个概念:
- 执行环境(execution context)
- 活动对象(activation object)
- 变量对象(variable object)
当某个函数被调用时,会创建一个执行环境及相应的作用域链,然后使用arguments和其他命名参数的值来初始化函数的活动对象,但在作用域链中, 外部函数的活动对象始终处于第二位,外部函数的外部函数活动对象处于第三位….直至终点的全局执行环境。
每个执行环境都有一个表示变量的对象即变量对象,当调用foo()时,会创建一个包含arguments、number的活动对象(局部活动对象),全局执行环境的变量对象(foo 【调用的地方,敞开在全局环境中,区分foo()函数是局部环境的变量】)在foo()执行环境的作用链中则处于第二位,全局环境的变量对象始终存在,这里foo()函数是局部环境的变量,只在函数执行的过程中存在
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来说,当函数执行完毕后,局部活动对象就会被销毁了(foo()函数以及arguments、number),但是对于我们上面这个例子代码中的情况并非如此,因为这里有闭包。虐你千面遍!
再来看看上面刚被搁置一旁的那句话:之所以可以访问外部的变量,是因为内部函数的作用域链中包含了外部函数的作用域
bar()函数从foo()中被返回后,它的作用域链被初始化为包含foo()函数的活动对象和全局变量对象,这样,bar()就可以访问在foo()中定义的所有变量,还有就是,当foo()函数被执行完毕后,其活动对象也不会被销毁,因为bar()的作用域链还在引用这个活动对象,换句话说,当foo()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直至bar()函数被销毁后,foo()的活动对象才会被销毁!晕没,我是有点晕了。
- 嵌套函数的返回
对上面的代码作一点修改,像下面这样,把bar()返回
function foo(){
var number = 123;
function bar(){
alert(number);
alert("bar is called !");
}
return bar;
}
foo();
//结果是:function bar(){
// alert(number);
// alert("bar is called !");
// }
返回的是一个Function对象,对比一下这里….,现在接下来把它赋给一个变量,像下面这样:
var barGet = foo();
barGet();
//123
//bar is called !
结果说明 ,可以从函数foo()的外部调用bar(),进一步说,函数foo()的局部变量在函数foo()被调用后依然存在!这和java中语法是相反的,java中的局部变量离开函数后,局部变量就会失效!
接下来看一个闭包比较有意思的地方:闭包与执行环境
function foo(arg){
var number = arg + 10;
function bar (){
alert("number is " + number);
}
return bar;
}
var bar1 = foo(10);
var bar2 = foo(20);
bar1(); // number is 20
bar2(); // number is 30
var number = 50;
bar2(); // number is 30
虽然后面把number设成了全局变量,并赋给了新值 ,但是调用 bar2()时,number 的值仍然是30, 所以可以这么理解闭包:闭包指的是一种特殊的函数,这种函数会在被调用时保持当时的变量名查找的执行环境。
再通过下面的这段代码理解一下上面的那句话:
fucntion foo(arg){
var number = 123;
function bar(){
alert(number);
}
number++;
function bar1(){
alert(number);
}
return [bar,bar1];
}
var bar = foo(100);
bar[0](); // 124
bar[1](); // 124
两个结果都是124,再回过头看看前面加粗的那一句话,number作为foo()函数的局部变量,也就是说因为bar()是一个闭包的原因,在foo()函数内部的执行环境下,number的值是固定的,不管number++放在bar()前还是后面。亦或是放在bar()函数内部,结果都是一样的。