闭包是一个比较抽象的概念,水有点深,很容易绕进去,为了方便自己总结,先把从书本《javascript权威指南》中学到的一些点放在博客上面。

  闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制等等。

  一:函数作用域

  在js中,函数作用域是说变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。这与C/C++等的块级作用域有所不同,所以大家在js中不要认为一个对象也是一个作用域。

  还有很重要的一点就是声明提前,只要通过var 定义了变量,不管是否赋值,这个变量始终存在于这个函数体内部。

例子1

1
2
3
4
5
6
7
8
var scope="global"; 
var a = 1;
function t(){
var scope="local"
console.log(scope); //local
}
t();

例子2

1
2
3
4
5
6
7
var scope="global";  
function t(){
console.log(scope); //undefined
var scope="local"
console.log(scope); //local
}
t();

 


  二、作用域链
 

作用域链图中很明确的表示出:在变量解析过程中首先查找局部的作用域,然后查找上层作用域。

 

在代码一的函数当中没有定义变量i,于是查找上层作用域(全局作用域),进而进行输出其值。但是在代码二的函数内定义了变量i(无论是在alter之后还是之前定义变量,都认为在此作用域拥有变量i),于是不再向上层的作用域进行查找,直接输出i。但是不幸的是此时的局部变量i并没有赋值,所以输出的是undefined。

代码一

1
2
3
4
5
var i = 10;   
function a() {
alert(i);
};
a(); //10

 

代码二

1
2
3
4
5
6
var i=10;   
function a() {
alert(i);
var i = 2;
};
a(); //undefined

 

三、内存回收机制

了解了作用域链,我们再来看看js的内存回收机制,一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了,对应的内存空间也就被回收了。

下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用。但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了。所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来,也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收。

 

四、闭包

我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁


例子1

1
2
3
4
5
6
7
8
9
10
11
12
function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,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是一个闭包,由于闭包使js的GC不会回收变量i,因为变量result的执行依赖于函数foo中的局部变量i。由于函数foo执行的时候,只是定义了result这个数组函数,所以最后的输出结果是3、3、3。

对比例子3,由于result这个数组是一个没有自执行的有传参的匿名函数,储存的是有传参的匿名函数(传入了当前的i),所以输出的结果是0、1、2。

例子2

1
2
3
4
5
6
7
8
9
10
11
12
13
var result=[];
function foo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=function(){
alert(i)
}
}
};
foo();
result[0](); // 3
result[1](); // 3
result[2](); // 3

result[0] = result[1] = result[2] = function(){  alert(i)  }

例子3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var result=[];
function foo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=(function(j){
return function(){
alert(j);
};
})(i);
}
};
foo();
result[0](); // 0
result[1](); // 1
result[2](); // 2

result[0] = result[1] = result[2] = function(){  alert(j)  }

posted on 2016-05-04 20:08  lyrezz  阅读(192)  评论(0编辑  收藏  举报