JavaScript內(nèi)存管理

JS內(nèi)存回收

JS 有自動垃圾回收機制,就是找出那些不再繼續(xù)使用的值,然后釋放其占用的內(nèi)存。

垃圾回收算法:

  • 引用計數(shù)垃圾收集
  • 標(biāo)記清楚算法

內(nèi)存泄露

如果連續(xù)五次垃圾回收之后,內(nèi)存占用一次比一次大,就有內(nèi)存泄漏。

在 Chrome 瀏覽器中,我們可以這樣查看內(nèi)存占用情況

  1. 打開頁面,右鍵選擇檢查,打開開發(fā)者工具
  2. 在頂部勾選 Memory
  3. 模擬用戶操作,并進行多次內(nèi)存快照

判定當(dāng)前是否有內(nèi)存泄漏:

  1. 多次快照后,比較每次快照中內(nèi)存的占用情況,如果呈上升趨勢,那么可以認(rèn)為存在內(nèi)存泄漏
  2. 某次快照后,看當(dāng)前內(nèi)存占用的趨勢圖,如果走勢不平穩(wěn),呈上升趨勢,那么可以認(rèn)為存在內(nèi)存泄漏

在 Node環(huán)境 查看內(nèi)存情況

console.log(process.memoryUsage());
// { 
//     rss: 27709440,
//     heapTotal: 5685248,
//     heapUsed: 3449392,
//     external: 8772 
// }

該對象包含四個字段,單位是字節(jié),含義如下:

  • rss(resident set size):所有內(nèi)存占用,包括指令區(qū)和堆棧。
  • heapTotal:"堆"占用的內(nèi)存,包括用到的和沒用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎內(nèi)部的 C++ 對象占用的內(nèi)存。

常見內(nèi)存泄露:

1、非必要的全局變量。如下代碼,就是創(chuàng)建了兩個全局變量name1,name2

function foo() {
    name1 = 'xyj'; // 沒有聲明變量 實際上是全局變量 => window.name1
    this.name2 = 'why' // 全局變量 => window.name2
}
foo();

2、被遺忘的定時器和回調(diào)函數(shù)

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); // 每 5 秒調(diào)用一次

如果后續(xù) renderer 元素被移除,整個定時器實際上沒有任何作用。 但如果你沒有回收定時器,整個定時器依然有效, 不但定時器無法被內(nèi)存回收, 定時器函數(shù)中的依賴也無法回收。在這個案例中的 serverData 也無法被回收。

3、閉包

在 JS 開發(fā)中,我們會經(jīng)常用到閉包,一個內(nèi)部函數(shù),有權(quán)訪問包含其的外部函數(shù)中的變量。 下面這種情況下,閉包也會造成內(nèi)存泄露:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) // 對于 'originalThing'的引用
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log("message");
    }
  };
};
setInterval(replaceThing, 1000);

這段代碼,每次調(diào)用 replaceThing 時,theThing 獲得了包含一個巨大的數(shù)組和一個對于新閉包 someMethod 的對象。 同時 unused 是一個引用了 originalThing 的閉包。

這個范例的關(guān)鍵在于,閉包之間是共享作用域的,盡管 unused 可能一直沒有被調(diào)用,但是 someMethod 可能會被調(diào)用,就會導(dǎo)致無法對其內(nèi)存進行回收。 當(dāng)這段代碼被反復(fù)執(zhí)行時,內(nèi)存會持續(xù)增長。

function makeAdder(count) {
  return function (num) {
    return count + num
  }
}

var add5 = makeAdder(5)
console.log(add5(6))

這段代碼中makeAdder函數(shù)執(zhí)行完畢,正常情況下我們的AO對象會被釋放; 但是因為在0xb00的函數(shù)中有作用域引用指向了這個AO對象,所以它不會被釋放掉;

解決方案 add5 = null

4、DOM 引用

很多時候, 我們對 Dom 的操作, 會把 Dom 的引用保存在一個數(shù)組或者 Map 中。

var elements = {
    image: document.getElementById('image')
};
function doStuff() {
    elements.image.src = 'http://example.com/xxx.png';
}
function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // 這個時候我們對于 #image 仍然有一個引用, Image 元素, 仍然無法被內(nèi)存回收.
}
復(fù)制代碼

上述案例中,即使我們對于 image 元素進行了移除,但是仍然有對 image 元素的引用,依然無法對齊進行內(nèi)存回收。

另外需要注意的一個點是,對于一個 Dom 樹的葉子節(jié)點的引用。 舉個例子: 如果我們引用了一個表格中的td元素,一旦在 Dom 中刪除了整個表格,我們直觀的覺得內(nèi)存回收應(yīng)該回收除了被引用的 td 外的其他元素。 但是事實上,這個 td 元素是整個表格的一個子元素,并保留對于其父元素的引用。 這就會導(dǎo)致對于整個表格,都無法進行內(nèi)存回收。所以我們要小心處理對于 Dom 元素的引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容