学习记录:闭包!

164 阅读2分钟

学习资料

这位大佬的文章

学习记录

闭包是什么

从功能角度,闭包让你能通过一个内部函数(inner function)访问这个内部函数的上一层函数(outer function)的Scope。

closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

闭包怎么用

基础使用

略过

会出现什么问题?内存泄漏

例子1:

function fun() {
    let arr = Array(10000000);
    return function () {
        console.log(arr); // 使用了 arr
    };
}
window.f = fun(); // 通过内部函数(inner function)持久存在,导致函数的上一层函数的scope(即闭包)无法被回收

例子2:

function fun() {
    let arr = Array(10000000);
    function fun1() {
        // arr 加入 Closure
        console.log(arr);
    }
    return function fun2() {};
}
window.f = fun(); // 即使长久持有的内部函数(inner function)不引用 arr,也会对其造成闭包无法回收

闭包原理(为什么)

简单解释

闭包是创建于外部函数(outer function),被所有内部函数(inner function)共同持有。

即 f 函数内,两个内部函数(fa,fb), fb 函数即使没用变量v1,fa函数用了,那么fb也会持有v1。所以当内部但凡有一个函数被外部环境长期持有,整个函数内被闭包的变量都会无法释放。

详细解释

let t = 111;
function fun() {
    let a = 1,
        b = 2,
        c = 3;
    function fun1() {
        console.log(a);
    }
    fun1();
    let obj = {
        fun2() {
            console.log(b);
        },
    };
}

fun();
  1. 前提1: 每个函数及全局都会拥有自己的[[Scopes]]作用域。
  2. 看上面的代码。首先进行进行全局扫,扫到 Global -> {t, fun}
  3. 执行到 fun()。扫fun() -> {a, b, c, fun1, obj}
  4. 整一个空的 Closure。暂时称为 Closure_fun
  5. 发现 fun1()。扫fun1() -> {a}。
  6. Closure_fun { a }。 a 放入 Closure_func。
  7. fun1()[[Scopes]] 会加入一个 Closure ,就是 Closure_func
  8. 发现 fun2() 。扫fun2() -> {b}。
  9. Closure_fun { a, b }。 b 放入 Closure_func。
  10. fun2()[[Scopes]] 会加入一个 Closure ,就是 Closure_func

测试一下

let theThing = null;
function replaceThing () {
    let leak = theThing;
    function unused() {
        if (leak) {
        }
    }

    theThing = {
        longStr: new Array(1000000),
        someMethod: function () {},
    };
};

let index = 0;
while (index < 100) {
    replaceThing();
    index++;
}
  1. Global -> { theThing, index, replaceThing }
  2. theThing(1) = null // (1) 第1次赋值
  3. replaceThing -> { theThing, leak, unused, someMethod }
  4. Clousure_replaceThing(1) { leak->theThing(1)->null } // 因为 unused 引用了
  5. theThing(2) = {...} // (2) 第2次赋值
  6. theThing(2).someMethod.[[Scopes]].Closure { leak->null }
  7. 循环
  8. Clousure_replaceThing(2) { leak->theThing(2) }
  9. theThing(3) = {...} // (3) 第3次赋值
  10. theThing(3).someMethod.[[Scopes]].Closure { leak->theThing(2) }
  11. 循环
  12. ...

可以看到所有的 theThing 都在(不同的)闭包里,即使从 theThing 的角度看并没有引入上次的 theThing

总结

上面是从最实用的角度进行表述,具体V8如何进行闭包的过程是模糊表述的,我也不知道具体是什么流程。

切记闭包是本函数创建给所有内部函数共用的,基本就可以分析出具体是如何发生泄漏。