JS内存泄漏实例解析

今天忽然想到一个问题,let的块级做用域,以及闭包的变量引用功能颇有意思(这脑洞咋联想到一块儿的,囧)。。闭包的使用会影响浏览器的GC过程。那么:es6

  • JS 对象何时会被自动回收
  • 如何使用正确使用闭包,并避免内存泄漏?

先看一个经典例子,循环异步打印问题(没耐心的直接跳最后一个实例(^▽^))浏览器

// 想异步打印1到5
for(var i=1; i<=5;i++) {
    setTimeout(function(){
        console.log("print: " + i);
    }, i*1000)
}
// 结果
print: 6
print: 6
print: 6
print: 6
print: 6
复制代码

因为是异步调用打印函数,因此等调用这个函数时,循环已经结束,i变成了6,因此连着打印5个6。bash

第二种状况,若是用let 来声明i,let 和var 相比至少有以下特性:闭包

  • let声明的变量拥有块级做用域
  • 形如for (let x...)的循环在每次迭代时都为x建立新的绑定(深度复制
// 1到5
for(let i=1; i<=5;i++) {
    setTimeout(function(){
        console.log("print: " + i);
    }, i*1000)
}
// 结果
print: 1
print: 2
print: 3
print: 4
print: 5
复制代码

这种状况下直接经过let, 实际上给每一次回调函数的注册,建立了一个闭包,因此打印正常。异步

第三种状况,经过手动建立闭包也能够实现相似效果。每次循环内,返回一个函数引用当时的变量 i,这样其实是从新分配了内存来存储i 的值,而不是单纯的引用内存地址。 尼玛内存蹭蹭往上涨,不过这么点数据彻底不用担忧函数

// 1到5
for(var i=1; i<=5;i++) {
    setTimeout((function(){
        var b = i; //install timer的时候引用 i 而且return 一个函数
        return function(){
            console.log("print: " + b);
        }
    })(), i*1000)
}
复制代码

这个例子很好地说明了闭包对内部变量内存地址的保留做用(循环1w次就深度复制了1w份i )。但闭包和全局变量的不当使用可能会致使内存泄漏,内存居高不下甚至标签页直接挂掉。工具

JS 变量在浏览器内存中是否被GC 回收要看这个变量所在做用域的生命周期和变量是否被别人引用:post

  • 若是是函数内部声明的变量,而且没有任何外部变量引用,则函数执行完就销毁。若是有引用,则该内部变量会一直游离于内存中

JS 对象(引用类型)是存储在内存堆heap中,能够经过Chrome Debug Tool的 Profile 工具生成Heap SnapShot 来查看。ui

最后看一个活生生的实例,不出意外分分钟内存占用1Gthis

function Test()  
{  
    this.obj= {};
    this.index = 1;
    this.timer = null;
    var cache = []; // 内部变量,内存隐患...
    this.timer = window.setInterval(() =>{
        this.index += 1; 
        this.obj = {
            val: '_timerxxxxxbbbbxx_' + this.index,
            junk: [...cache]
        };
        cache.push(this.obj);
    }, 1);  
    console.warn("create Test instance..");
}  
test = new Test(); // JS对象开启定时器不断分配内存
复制代码

啰嗦几句,这个例子的关键在于内部变量cache被外部的异步函数(定时器)引用。 若是不清除定时器,只是把Test类的实例手动设为null,也无济于事,cache还会继续占用内存。

在清除定时器,而且把Test类的实例设为null后才成功回收垃圾

Test.prototype.destroy = function(){
    clearInterval(this.timer);
}
function d() {
    // 取消定时器并销毁Test 实例
    test.destroy();
    test = null;
    console.warn("destroyed test instance..");
}
复制代码

清除内部变量cache的引用后,内存堆大小马上降低了40MB.

总结:

  • 函数内部不用的局部变量及时清理,在清理时要考虑ta的全部引用函数。
  • 非得引用局部变量,请用非匿名函数,不然难以销毁引用。

我的看法,说得不对之处欢迎评论指正 : )

参考文章:

es6-in-depth-let-and-const

谈一谈Javascript内存释放那点事

JS内存管理

相关文章
相关标签/搜索