js引起記憶體洩露的幾種情況分析

antzone發表於2017-04-03

記憶體洩露是指一塊被分配的記憶體既不能使用,又不能回收,直到瀏覽器程式結束。

在C++中,因為是手動管理記憶體,記憶體洩露是經常出現的事情。

而現在流行的C#和Java等語言採用了自動垃圾回收方法管理記憶體,正常使用的情況下幾乎不會發生記憶體洩露。

瀏覽器中也是採用自動垃圾回收方法管理記憶體,但由於瀏覽器垃圾回收方法有bug,會產生記憶體洩露。

記憶體洩露引起方式一:

當頁面中元素被移除或替換時,若元素繫結的事件仍沒被移除,在低版本IE中不會作出恰當處理,此時要先手工移除事件,不然會存在記憶體洩露,看如下程式碼:

[HTML] 純文字檢視 複製程式碼
<div id="myDiv"> 
<input type="button" value="Click me" id="myBtn"> 
</div> 
<script type="text/javascript"> 
var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
document.getElementById("myDiv").innerHTML = "螞蟻部落"; 
} 
</script>

以上程式碼修改如下:

[HTML] 純文字檢視 複製程式碼
<div id="myDiv"> 
<input type="button" value="Click me" id="myBtn"> 
</div> 
<script type="text/javascript"> 
var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
btn.onclick = null; 
document.getElementById("myDiv").innerHTML = "螞蟻部落"; 
} 
</script>

或者採用事件委託:

[JavaScript] 純文字檢視 複製程式碼
<div id="myDiv"> 
<input type="button" value="Click me" id="myBtn"> 
</div> 
<script type="text/javascript"> 
document.onclick = function(event){ 
  event = event || window.event; 
  if(event.target.id == "myBtn"){ 
    document.getElementById("myDiv").innerHTML = "螞蟻部落"; 
  } 
}

記憶體洩露方式二:

[JavaScript] 純文字檢視 複製程式碼
var a=document.getElementById("#xx"); 
var b=document.getElementById("#xxx"); 
a.r=b; 
b.r=a;

對於純粹的ECMAScript物件而言,只要沒有其他物件引用物件 a、b,也就是說它們只是相互之間的引用,那麼仍然會被垃圾收集系統識別並處理。但是在低版本的 Internet Explorer 中,如果迴圈引用中的任何物件是 DOM 節點或者 ActiveX 物件,垃圾收集系統則不會發現它們之間的迴圈關係與系統中的其他物件是隔離的並釋放它們。最終它們將被保留在記憶體中,直到瀏覽器關閉。 

記憶體洩露方式三:

[JavaScript] 純文字檢視 複製程式碼
var elem = document.getElementById('test'); 
elem.addEventListener('click', function() { 
  alert('You clicked ' + elem.tagName); 
});

這段程式碼把一個匿名函式註冊為一個DOM結點的click事件處理函式,函式內引用了一個DOM物件elem,就形成了閉包。

這就會產生一個迴圈引用,即:DOM->閉包->DOM->閉包...DOM物件在閉包釋放之前不會被釋放;

而閉包作為DOM物件的事件處理函式存在,所以在DOM物件釋放前閉包不會釋放,即使DOM物件在DOM tree中刪除,由於這個迴圈引用的存在,DOM物件和閉包都不會被釋放。可以用下面的方法可以避免這種記憶體洩露 :

[JavaScript] 純文字檢視 複製程式碼
var elem = document.getElementById('test'); 
elem.addEventListener('click', function() { 
  alert('You clicked ' + this.tagName); // 不再直接引用elem變數 
});

記憶體洩露方式四:

[JavaScript] 純文字檢視 複製程式碼
function bindEvent() { 
  var obj=document.createElement("XXX"); 
  obj.onclick=function(){ 
    //Even if it's a empty function 
  } 
}

閉包非常容易構成迴圈引用。如果一個構成閉包的函式物件被指定給,比如一個 DOM 節點的事件處理器,而對該節點的引用又被指定給函式物件作用域中的一個活動(或可變)物件,那麼就存在一個迴圈引用。 [

DOM_Node.onevent -<function_object.[[scope]] -<scope_chain -<Activation_object.nodeRef -<DOM_Node。

形成這樣一個迴圈引用是輕而易舉的,而且稍微瀏覽一下包含類似迴圈引用程式碼的網站(通常會出現在網站的每個頁面中),就會消耗大量(甚至全部)系統記憶體。 解決之道,將事件處理函式定義在外部,解除閉包 :

[JavaScript] 純文字檢視 複製程式碼
function bindEvent(){ 
  var obj=document.createElement("XXX"); 
  obj.onclick=onclickHandler; 
} 
function onclickHandler(){ 
  //do something 
}

或者在定義事件處理函式的外部函式中,刪除對dom的引用(題外,《JavaScript權威指南》中介紹過,閉包中,作用域中沒用的屬性可以刪除,以減少記憶體消耗。) 

[JavaScript] 純文字檢視 複製程式碼
function bindEvent(){ 
  var obj=document.createElement("XXX"); 
  obj.onclick=function(){ 
    //Even if it's a empty function 
  } 
  obj=null; 
}

記憶體洩露方式五:

[JavaScript] 純文字檢視 複製程式碼
a = {p: {x: 1}}; 
b = a.p; 
delete a.p;

執行這段程式碼之後b.x的值依然是1.由於已經刪除的屬性引用依然存在,因此在JavaScript的某些實現中,可能因為這種不嚴謹的程式碼而造成記憶體洩露。所以在銷燬物件的時候,要遍歷屬性中屬性,依次刪除。 

記憶體洩露方式六:

自動型別裝箱轉換在低版本ie系列中會導致記憶體洩露。

[JavaScript] 純文字檢視 複製程式碼
var str="antzone"; 
console.log(str.length);

str本身是一個string而非object,它沒有length屬性,所以當訪問length時,JS引擎會自動建立一個臨時String物件封裝str,而這個物件一定會洩露。這個bug匪夷所思,所幸解決起來相當容易,記得所有值型別做.運算之前先顯式轉換一下:

[JavaScript] 純文字檢視 複製程式碼
var str="antzone"; 
console.log(new String(str.length);

相關文章