導致JS內存泄漏的幾種情況

內存泄漏是開發中很常見的問題,即使使用具有自動管理內存的語言,也有可能出現內存泄漏的情況,內存泄漏可能會引起變慢、延遲、崩潰等問題。

要解決內存泄漏問題,首先要弄懂什麼是內存泄漏,什麼情況下會導致內存泄漏。這樣,當出現內存泄漏時才知道如何應對。

什麼是內存泄漏?

內存泄漏是指不再用到的內存,沒有及時釋放。既不能使用,又不能回收。

程序的運行需要內存。對於持續運行的進程,如果不及時釋放不再用到的內存,內存佔用越來越高,輕則影響系統性能,重則導致進程崩潰。

要了解 JS 內存泄漏的幾種情況,我們首先來了解一下 JS 的內存是如何管理的,即 JS 的垃圾收集機制。

垃圾收集機制

Javascript 具有自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程中的使用的內存。所需內存的分配 以及無用的回收 完全實現了自動管理。

JavaScript 垃圾回收機制很簡單:找出不再使用的變量,然後釋放掉其佔用的內存。但是這個過程不是時時的,因爲其開銷比較大,所以垃圾回收器會按照固定的時間間隔週期性的執行。

JavaScript 中最常用的垃圾收集方式有 2 種:標記清除引用計數

1)“標記清除”——當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲“進入環境”。當變量離開環境時,這將其 標記爲“離開環境”。

從邏輯上講,永遠不能釋放進入環境的變量所佔的內存,因爲只要執行流進入相應的環境,就可能用到它們。

function fun(){
    var a = 1;//被標記 進入環境
    var b= 2;//被標記 進入環境
}
fun();//執行完畢之後,a,b 被標記離開環境,被回收

如果我們的代碼寫法不當,會讓變量一直處於“進入環境”的狀態,無法被回收。

2)“引用計數”——語言引擎有一張”引用表”,跟蹤記錄每個值被引用的次數
如果一個值的引用次數是0,就表示這個值不再用到了,因此可以將這塊內存釋放。

如果一個值不再需要了,引用數卻不爲0,垃圾回收機制無法釋放這塊內存,從而導致內存泄漏。

那些很佔空間的值,一旦不再用到,你必須檢查是否還存在對它們的引用。如果是的話,就必須手動解除引用

function test(){
 var a = {} ; //a的引用次數爲0 
 var b = a ; //a的引用次數加1,爲1 
 var c = a; //a的引用次數再加1,爲2
 var b ={}; //a的引用次數減1,爲1
}

導致內存泄漏的幾種情況

1. 意外的全局變量

function leaks(){  
    leak = 'xxxxxx';//leak 成爲一個全局變量,不會被回收
}

調用完函數以後,變量仍然存在,導致泄漏.
你可以通過加上 ‘use strict’ 啓用嚴格模式來避免這類問題, 嚴格模式會阻止你創建意外的全局變量.

2. 閉包

閉包可以維持函數內局部變量,使其得不到釋放。
解決辦法:在函數外部定義事件處理函數,解除閉包。或在閉包中,刪除沒用的屬性以減少對內存的消耗。或在外部函數中刪除對DOM的引用

function bindEvent() 
{ 
    var obj=document.createElement("XXX"); 
    obj.onclick=function(){ 
        //Even if it's a empty function 
    } 
    obj=null; 
}

3. 未清除 dom 元素的引用

dom 元素移除,但 對 dom 元素的引用沒有解除,會導致內存泄漏。
解決辦法:手工移除。

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image')
}

function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
}

function removeButton() {
    document.body.removeChild(document.getElementById('button'));
  // 雖然我們用removeChild移除了button, 但是還在elements對象裏保存着#button的引用
 // 換言之, DOM元素還在內存裏面.
}

4.循環引用

循環引用 在引用計數策略下會導致內存泄漏,標記清除不會。
解決辦法:手工解除循環引用。

function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
} 
fn();

a和b的引用次數都是2,fn()執行完畢後,兩個對象都已經離開環境。
在標記清除方式下是沒有問題的,但是在引用計數策略下,a和b的引用次數不爲0,不會被垃圾回收器回收內存。如果fn函數被大量調用,就會造成內存泄漏。

IE中的BOM和DOM中的對象使用C++以COM(component Object Model,組件對象模型)對象的形式實現而COM對象的垃圾收集機制採用的是引用計數策略。換句話說,只要在IE中涉及COM對象,就會存在循環引用的問題。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
//手工斷開它們之間的連接
myObject.element=null;
element.someObject=null;

5. 被遺忘的計時器或回調

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

如果 id 爲 Node 的元素從 DOM 中移除, 該定時器仍會存在, 同時, 因爲回調函數中包含對 someResource 的引用, 定時器外面的 someResource 也不會被釋放.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章