从JS引擎V8的角度看待闭包

闭包的设计目的是为了存储私有变量,延长变量的生命周期,只有特定的接口才能访问该私有变量,可以防止防止全局变量命名冲突。
闭包常见例子:

        function fn() {
            var a = 1;
            function foo1() {
                console.log(a++);
            }
            return foo1;
        }
        var foo2 = fn();
        foo2();
        foo2();

这个闭包包括了javascript的三个特性:
1、父元素嵌套了子元素
2、子元素里引用了父元素里的变量
3、函数是一等公民,可以作为返回值返回给变量

现在看看V8引擎是如何实现这个闭包这个技术的

1、创建全局上下文,函数fn被创建,V8将函数声明转换为函数对象,保存作用域链到[[scope]]属性中,将代码转换成字符串保存到code属性中

fn.[[scope]]={
globalContext.VO;
}

2、创建函数执行上下文,fn函数执行上下文被压入执行上下文栈顶端

ECStack = [
    fnContext,
    globalContext
];

3、进行预解析,赋值函数对象中的[[scope]]属性值创建作用域链

fnContext = {
    Scope: fn.[[scope]],
}

4、用arguement创建活动对象,随后初始化活动对象,加入形参,变量提升,函数提升

fnContext = {
    AO: {
        arguments: {
            length: 0
        },
        a: undefined
    },
    Scope: [AO, [[Scope]]]
}

此时如果碰到了函数,V8不会直接跳过,而是会快速地解析内部代码,判断是否使用了外部变量,如果使用了,那么会复制一份保存到堆内存中其名为closure(闭包),并将closure保存到子函数的[[scope]]属性中,该赋值操作会在父函数执行上下文销毁之前进行。在这个例子中,foo1使用了fn中的变量,会将其保存到[[scope]]中,可以在foo1的prototype中看到。

从JS引擎V8的角度看待闭包_第1张图片
5、执行函数中的代码,并修改AO中属性的值

fnContext = {
    AO: {
        arguments: {
            length: 0
        },
        a: 1
    },
    Scope: [AO, [[Scope]]]
}

6、最后返回了foo1的指针给全局变量foo2,函数上下文从执行上下文栈中弹出

ECStack = [
    globalContext
];

为什么闭包对象没有被垃圾回收清除

根据垃圾回收规则,没有被引用的对象会被回收,而fn函数最后把闭包函数对象的指针返回给了全局变量foo2,虽然foo1的父元素执行上下文最后出栈,foo1中指向闭包函数对象的指针也被销毁,但是全局变量依然保存了,导致闭包函数对象一直保留在内存中,而Closure对象一直保存在[[scope]]中,所以不会被清除,下次再次调用foo2时可以直接从堆内存中取出a。

为什么返回给全局变量可以叠加,不返回就不行?

        function fn() {
            var a = 1;
            function foo1() {
                console.log(a++);
            }
            return foo1;
        }
        var foo2 = fn();
        foo2();
        foo2();

在这里插入图片描述

function fn() {
            var a = 1;
            function foo1() {
                console.log(a++);
            }
            return foo1;
        }
        fn()();
        fn()();

在这里插入图片描述

如果返回给全局变量可以实现叠加器的效果,因为每次调用的都是指向同一个对象
而如果不返回,那么闭包函数对象的生命周期到父函数执行上下文销毁就结束了,会被回收,那么对象中的闭包对象也就不复存在了,再次调用的话,就是重新分配内存空间。

科里化函数里的闭包

        function sum(a) {
            return function (b) {
                return function (c) {
                    return a + b + c;
                }
            }
        }
        var sum1 = sum(1)(2)(3);

执行结果为
在这里插入图片描述
看看function©这个匿名闭包函数的[[scope]]

        function sum(a) {
            return function (b) {
                return function (c) {
                    return a + b + c;
                }
            }
        }
        var sum1 = sum(1)(2);
        console.log(sum1.prototype);

在这里插入图片描述
保存了两个闭包函数对象,分别存储了变量a和b

你可能感兴趣的:(Javascript,函数闭包)