# 闭包

闭包不是 JS 独有的东西. 在 JS 中, 闭包指, 给予父级作用域拥有访问子作用域(函数作用域)的属性的权利. 闭包的一个常见形式, 就是在一个函数内部编写一个新的函数.

在作用域一章中, 我们讨论过, 父作用域无法直接访问子作用域中的内容, 因为父作用域的作用域链太短了, 没有与子作用域相关的部分.

回忆一下 Java 中, 遇到类似问题时, 是不是也有相似的操作, 比如让一个方法返回这个类中的内部匿名类, 让外围的代码拥有访问内部匿名类的权利.

# 闭包的实现

开始的部分也写了, 一般使用函数的套娃写法:

function test() {
    var a = 10; 
    // 返回一个内部匿名函数的引用
    return function() {
        return a;
    }
}

var b = test(); // 通过调用 test 方法, 或的其内部的匿名方法的引用. 
cosole.log(b()); // 调用 b 方法, 将打印出 a 的值, 即 10 .
1
2
3
4
5
6
7
8
9
10

TIP

这里 test() 方法为什么不直接返回 a ? 哦, 用来说明问题而已, 领会精神.

# 闭包的本质

闭包的实现 中那段代码内的匿名函数拥有自己的作用域链, 同时这个作用域链中包含外部函数的变量对象的引用. 当我们调用外部函数获得内部函数的引用的时候, 也就等于获得了外部函数的属性对象的访问权 (当然, 也包括内部函数).

或者我们可以给这种操作取个名字: 作用域上升.

# 循环与闭包

循环这个主题, 能够很好地检验是否理解了闭包. 当然这些例子看起来会有一点"脱裤子放屁"的感觉.

# 经典例子

function test() {
    var result = [];
    for (var i = 0; i < 10; i++) {
        result[i] = function() {
            return i;
        }
    }
    return result;
}

// 执行 test 方法, 同时打印结果 
var result = test();
for (var i = 0; i < 10; i++) {
    var func = result[i];
    // 直觉上, 这里会打印 0 到 9 , 实际上是 10 个 10; 
    console.log(func()) 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上面的代码在 for 循环的帮助下, 一共创建了 10 个匿名函数的引用, 但是这些匿名函数的作用域链中包含的是同一个 test() 方法的属性对象. 所以有了这样诡异的答案.

# 正确的姿势

我们在匿名函数外面再添加一层函数, 来包裹原来的函数. 只是通过参数 num 来注入循环索引 i , 像 4 行这样. 另外, 新添加的这个函数必须在 for 循环的过程中就被执行, 所以在 8 行, 添加了立即执行的操作 (i); 如果没有这个东西, 那么打印出来的就是新添加的函数本体.




 



 












function test() {
    var result = [];
    for (var i = 0; i < 10; i++) {
        result[i] = function(num) {
            return function() {
                return num;
            }
        }(i);
    }
    return result;
}

// 执行 test 方法, 同时打印结果 
var result = test();
for (var i = 0; i < 10; i++) {
    var func = result[i];
    // 这里会打印 0 到 9 
    console.log(func()) 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 内存泄露

不正确的闭包使用会导致内存泄漏, 这也是为什么当谈到闭包的时候, 人们总是很拒绝.

由于闭包会导致外部函数始终被内部函数引用的状态(属性对象的引用在匿名函数的作用域链手上), GC 没法子直接回收外部函数, 哪怕外部函数执行完毕.

为了避免这个问题, 最简单的就是直接干掉所有关于内部函数的引用, 还是那个例子:











 

function test() {
    var a = 10; 
    // 返回一个内部匿名函数的引用
    return function() {
        return a;
    }
}

var b = test(); 
cosole.log(b()); 
b = null;   // 世界安静了
1
2
3
4
5
6
7
8
9
10
11
Last Updated: 12/14/2019, 11:26:50 PM