chuanFE

对js闭包的理解及其应用

闭包的概念

简单来说,闭包指的是被持久化的函数以及它所保留的对外层变量的引用的组合。
具体来说,闭包就是保留了对自由变量的引用的函数。与面向对象方式使用类属性来保留数据不同,闭包是通过词法作用域来保留对于变量的引用的。词法作用域的最基本特性是:在嵌套的作用域中,某个作用域可以访问本层定义的变量,也可以访问外层的变量,但是不能访问内层的变量。对于 js 语言来说,最常见的作用域层级就是 var 声明所对应的函数级别作用域。首先,由于外层作用域无法访问内层作用域的变量,因此自由变量肯定不会是内层变量。其次,对于本层定义的变量来说,函数执行完毕后,这些变量就会被销毁,留待垃圾回收,因此本层的变量不具备保留引用的特性。所以最后的结论已经很明确,保留引用的自由变量只能是外层的变量。
因此,闭包是保留了对外层自由变量的引用的函数。

如何保留对外层变量的引用?这要求函数能以某种方式被持久化保留。

来个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function closure(){
var count = 0;
return function(){
console.log(++count);
}
}
var getCount = closure();
getCount(); // 输出 1
getCount(); // 输出 2
getCount(); // 输出 3
...

在这个例子中,调用 closure() 函数时,内层的匿名函数被作为返回值赋予了外部的 getCount 变量。由于内层的匿名函数需要使用外层的 count 变量,因此外层变量 count 就被匿名函数保留了引用。而每次调用 getCount() , count 变量的值都会被加一。
在这个例子中,内层的匿名函数以及它所保留的对于外层变量 count 的引用就构成了闭包。

如果内层函数没有用任何方式持久化,那么它也无法保留对于外层变量的引用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function noClosure(){
var count = 0;
function inner(){
console.log(++count);
}
inner();
}
noClosure(); // 输出 1
noClosure(); // 输出 1
noClosure(); // 输出 1

由于这个例子的 inner() 没有被持久化,调用时只能直接调用外层函数 noClosure() 。此时内层的 inner() 每次被调用后就结束了生命周期,相关的作用域也被销毁,因此 count 变量也不会被保留引用,这样每次调用 noClosure() 都只会输出 1 。由此可以进一步总结闭包的概念:闭包是以某种方式被持久化、并保留了对外层自由变量的引用的函数。实际的闭包指的是被持久化的函数以及它所保留的对外层变量的引用的组合。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。方便调用上下文的局部变量。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

我知道是不会有人点的,但万一有人想不开呢?