javascript闭包的理解

案例

首先看下闭包的一个简单案例

1
2
3
4
5
6
7
8
function A(){
function B(){
console.log("Hello haorooms!");
}
return B;
}
var c = A();
c();//Hello haorooms!

这个例子是一个比较简单的闭包,解释如下:

1
2
3
4
5
(1)定义了一个普通函数A
(2)在A中定义了普通函数B
(3)在A中返回B(确切的讲,在A中返回B的引用)
(4)执行A(),把A的返回结果赋值给变量 c
(5)执行 c()

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

闭包的作用

在上述例子中,B定义在A中,因此B依赖于A,而外部变量 c 又引用了B, 所以A间接的被 c 引用,也就是说,A不会被回收,会一直保存在内存中。

1
2
3
4
5
6
7
8
9
10
11
12
function A(){
var count = 0;
function B(){
count ++;
console.log(count);
}
return B;
}
var c = A();
c();// 1
c();// 2
c();// 3

count是A中的一个变量,它的值在B中被改变,函数B每执行一次,count的值就在原来的基础上累加1。因此,A中的count一直保存在内存中。

这就是闭包的作用,有时候我们需要一个模块中定义这样一个变量:希望这个变量一直保存在内存中但又不会“污染”全局的变量,这个时候,我们就可以用闭包来定义这个模块。

闭包的含义和理解

通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子:

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

这段代码中,generateClosure() 函数中有一个局部变量count,初值为 0。还有一个叫做 get 的函数,get 将其父作用域,也就是 generateClosure() 函数中的 count变量增加 1,并返回 count 的值。generateClosure() 的返回值是 get 函数。在外部我们通过 counter变量调用了 generateClosure() 函数并获取了它的返回值,也就是 get 函数,接下来反复调用几次 counter(),我们发现每次返回的值都递增了1。

让我们看看上面的例子有什么特点,按照通常命令式编程思维的理解,count 是generateClosure 函数内部的变量,它的生命周期就是 generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时,count 变量申请的空间也就被释放。问题是,generateClosure() 调用结束后,counter() 却引用了“已经释放了的” count变量,而且非但没有出错,反而每次调用 counter() 时还修改并返回了 count。这是怎么回事呢?

这正是所谓闭包的特性。当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。 上面例子中,当函数generateClosure() 的内部函数 get 被一个外部变量 counter 引用时,counter 和generateClosure()的局部变量就是一个闭包。如果还不够清晰,下面这个例子可以帮助你理解:

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

上面这个例子解释了闭包是如何产生的: counter1和counter2分别调用了generate-Closure() 函数,生成了两个闭包的实例,它们内部引用的 count 变量分别属于各自的运行环境。我们可以理解为,在 generateClosure() 返回 get 函数时,私下将 get 可能引用到的 generateClosure() 函数的内部变量(也就是 count 变量)也返回了,并在内存中生成了一个副本,之后 generateClosure() 返回的函数的两个实例 counter1和 counter2 就是相互独立的了。