LOADING

MiniKano的小窝


 

Javascript中的闭包与浏览器引擎优化问题

在复习JavaScript基础的时候,我写了一份演示闭包内存泄漏的Demo,但在运行时,并没有发现内存的明显变化:

<button id="add">创建数组对象</button>
<br />
<button id="des">销毁数组对象</button>
<script>
    const add = document.querySelector('#add')
    const des = document.querySelector('#des')

    function createArray() {
        var arr = new Array(1024 * 1024).fill(1);
        return function () {
            console.log('create done');
        }
    }

    let array = []

    //批量创建闭包
    add.addEventListener('click', () => {
        for (let i = 0; i < 100; i++) {
            array.push(createArray())
        }
        console.log(array,array.length);
    })

     //释放创建的闭包
     des.addEventListener('click', () => {
         array = []
         console.log('销毁成功');
     })
</script>

按照闭包的原理来说,createArray 函数执行后,返回的函数,已经捕获到了函数定义时的父级作用域,虽然我没有使用父级作用域中的变量,按照闭包的规则,这仍然是一个闭包
但实际上在浏览器运行代码时,内存并没有发生明显变化。

经过查询,我发现,虽然词法分析时候确实形成了闭包,但因为现代的Javascript引擎对闭包的实现进行了优化。

具体来说,只有当返回的函数实际使用了父级作用域中的变量时,才会保留这些变量在内存中的引用。

这种优化减少了不必要的内存占用,但并不改变闭包本身的概念。
所以,这实际上是一种引擎优化。

在V8引擎中被称之为:逃逸分析(Escape Analysis)上下文优化

逃逸分析(Escape Analysis): "如果一个变量不会被嵌套函数访问,它被视为“不逃逸”,可以分配到栈上或完全忽略。"

上下文优化: "在生成字节码的过程中,V8 会决定哪些变量需要分配到上下文对象(Context Object)中。"

了解了大概的原因,我们就可以修改一下这个测试例:

...
    function createArray() {
        var arr = new Array(1024 * 1024).fill(1);
        return function () {
            arr;
            console.log('create done');
        }
    }
...

只需要在返回的function中引用到需要使用的作用域中的变量,这个闭包就可以在实际环境中生效了

可以看到,闭包造成的内存泄漏还是很严重的 :鹿乃_奇怪的知识:

但我又发现了一个问题,这个销毁数组的方法array = []并不能断开闭包函数引用,GC并没有触发清理内存操作

这时我意识到闭包是存于数组内部的,而改变array的指向只是改变对数组的引用指向,而闭包的函数环境还是被先前的数组引用着,且这个引用是一个强引用(Strong Reference)

强引用是指在代码中明确地持有对对象的引用,使对象不能被垃圾回收。
只要存在强引用指向一个对象,该对象就会一直存在于内存中,垃圾回收器不会将其回收。

JavaScript 的垃圾回收机制(GC)会检测是否有任何活动的引用指向内存。如果闭包内仍然引用了外部变量,即使你更改 array 的指向,这些变量也无法被回收。

于是我写了一个循环,依次将数组中的值全部置为null

array.forEach((_,index)=>array[index]=null)
array = []

这样就能真正清空数组。

但此时我又发现一个更简单的方法:设置array的length值为0,这种方法也可以清空数组中的全部内容

length 设置为 0,不仅仅是改变数组的长度,而是直接移除数组中所有的元素。

array.length = 0
点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注