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內存管理

相關文章
相關標籤/搜索