闭包的定义和特性

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。

闭包有下面三个特性:

  1. 函数嵌套函数。
  2. 函数内部可以引用外部的参数和变量
  3. 参数和变量不会被垃圾回收机制回收

下面看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function f1() {
let i = 0;
const f2 = function() {
++i;
console.log(i);
}
return f2;
}
let fun = f1();
fun(); // 1
fun(); // 2
fun = null; // a 被回收

在上面这段代码中,fun一共运行了两次,第一次1,第二次2。这证明了函数f1中的局部变量i一直保存在内存中,并没有在fun调用后被自动清除。

为什么会这样呢?原因就在于f1f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

接下来看一个经典的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}
// 打印了5个5
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 0 1 2 3 4
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
})(i);
}
// 0 1 2 3 4
for (var i = 0; i < 5; i++) {
setTimeout((function(i) {
return function() {
console.log(i);
}
})(i), i * 1000);
}

下面有几句话帮助理解闭包:

  1. 闭包是一个有状态(不消失的私有数据)的函数

  2. 打破作用域规则的变量就是闭包