闭包
一直对Js的闭包有点迷糊,今天偶然读到Javascript权威指南第六版本,相当的厚,大概1000多页,Js博大精深,要熟悉精通需要大毅力大
功夫。闭包是一个比较抽象的概念,尤其是对js新手来说,但是他也是js能力提升中无法绕过的一环。但是他也是js能力提升中无法绕过的
一环,几乎每次面试必问的问题,因为在回答的时候你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实
际水平的.即使你没答对,也能让考官对你的水平有个评估.那么我先来说说我对js中的闭包的理解.
闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等.
一:函数作用域
先看一小段代码:1
2
3
4
5
6
7var scope="global";
function t(){
console.log(scope);
var scope="local"
console.log(scope);
}
t();
第一句输出的是: “undefined”,而不是 “global”
第二讲输出的是:”local”
你可能会认为第一句会输出:”global”,因为代码还没执行var scope=”local”,所以肯定会输出“global”。
我说这想法完全没错,只不过用错了对象。我们首先要区分Javascript的函数作用域与我们熟知的C/C++等的块级作用域。
在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。而Javascript压根没有块级作用域,而是函数作用域.
所谓函数作用域就是说:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
还有一点:声明提前1
2
3
4
5
6
7
8var scope="global";
var a = 1;
function t(){
console.log(scope);
var scope="local"
console.log(scope);
}
t();
二、作用域链
作用域链图中很明确的表示出:在变量解析过程中首先查找局部的作用域,然后查找上层作用域。在代码一的函数当中没有定义变量i,于是查找上层作用域(全局作用域),进而进行输出其值。但是在代码二的函数内定义了变量i(无论是在alter之后还是之前定义变量,都认为在此作用域拥有变量i),于是不再向上层的作用域进行查找,直接输出i。但是不幸的是此时的局部变量i并没有赋值,所以输出的是undefined。
代码一1
2
3
4
5var i = 10;
function a() {
alert(i);
};
a();
代码二1
2
3
4
5
6var i=10;
function a() {
alert(i);
var i = 2;
};
a();
三、内存回收机制
了解了作用域链,我们再来看看js的内存回收机制,一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来.也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收.
四、闭包
各种专业文献上的“闭包”(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
例子1
1 | function f1(){ |
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个
匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
例子2
1 | var result=[]; |
例子3
1 | var result=[]; |