糾結的IE瀏覽器記憶體洩漏的測試

冷月宮主發表於2012-02-12
前幾天在編寫程式碼高亮指令碼的時候,問了瓶子一個問題,就是在迴圈裡處理刪除DOM元素的時候,會動態改變NodeList的length,所以測試許久,最後發現是這個問題,狂暈。但是期間談到了一個關於removeChild的時候在IE下無法回收記憶體的洩漏問題,他展示了一個EXT裡針對IE使用的方法:

[javascript]
var div=document.getElementById("div");
var first=div.firstChild,next=first;
while(next){
var d=document.createElement("div");
d.appendChild(next);
d.innerHTML="";
next=div.firstChild;
}
///////////////////////////////////////////////////
//簡單的removeChild方式:
var div=document.getElementById("div");
var first=div.firstChild,next=first;
while(next){
        next.parentNode.removeChild(next);
next=div.firstChild;
}
[/javascript]

但是經過使用Drip工具(測試IE是否記憶體洩漏的工具,download),測試還是存在記憶體洩漏的問題,但是使用IE JS Leaks Detector卻啥也檢測不出來(全部的測試都檢測不出來,就連網上都吹捧的記憶體洩漏的方式也檢測不出來),還有使用了話說是Drip的增強版的sIEve(download),也測試不出來。既然這樣,那就暫且信任Drip吧。下面幾種傳說中的記憶體洩漏的方式都是在Drip下測試的。

在開始講述之前,先大概瞭解一下javascript的GC機制:

垃圾回收程式嘗試推斷何時可以安全地回收不再使用的變數,通常是通過判定程式是否能夠通過變數之間形成的引用網路到達該變數。當確信變數是不可達的,就在它上面標上可以回收的記號,並且在回收器的下一次清理中(可能在未來的任意時刻)釋放相關的記憶體。

也就是說,垃圾回收機制會定時的檢查程式中的物件,檢視它是否跟別的物件之間已經完全斷開了引用鏈而“孤單一人”,這時,垃圾回收機制就會回收這個物件的記憶體,否則,將不會回收。所以說,物件在使用完了之後,就應該被回收記憶體,而不是一直佔用著記憶體不放,導致瀏覽器的記憶體使用量節節飆升。

第一種:既然上面談到了關於removeChild,那就從它開始吧,通過Drip測試,簡單的使用removeChild刪除子節點的方式確實存在記憶體洩漏,但是使用了上面EXT使用的方式,也還是存在。經過一番搜尋,有文章說需要清除節點的全部屬性來實現記憶體的正確回收,那就進行了下面的測試。結果通過將節點的屬性都delete掉之後,Drip顯示沒有記憶體洩漏了。

[javascript]
var div=document.getElementById("div");
var first=div.firstChild,next=first;
while(next){
  div.removeChild(next);
  for(var k in next){
     delete next[k];
  }
  next=div.firstChild;
}
[/javascript]

第二種:將一個DOM物件和一個JS物件相互成為對方的屬性。對於這點,IE官方也都有說法:在IE6中,對於javascript object內部,jscript使用的是mark-and-sweep演算法,而對於javascript object與外部object(包括native object和vbscript object等等)的引用時,IE 6使用的才是計數器的演算法。也就是說,IE 6對於純粹的Script Objects間的Circular References是可以正確處理的,可惜它處理不了的是JScript與Native Object(例如Dom、ActiveX Object)之間的Circular References。所以,當我們出現Native物件(例如Dom、ActiveX Object)與Javascript物件間的迴圈引用時,記憶體洩露的問題就出現了。當然,這個bug在IE 7中已經被修復了。(Fuck,難怪我用Drip測試不出來(系統是IE8的核心))。下面是我的一個測試:

[javascript]
function Encapsulator(element){
    this.elementReference = element;
    element.expandoProperty = this;
}
function SetupLeak2(){
    var obj=new Encapsulator(document.getElementById("test"));
    document.body.removeChild(document.getElementById("test"));
    //alert(document.getElementById("test").expandoProperty);   出現錯誤
//說明從element.expandoProperty —> obj的引用已經斷開了
//但是從obj.elementReference到element的引用依然存在,
//這樣的話在IE6下element就無法回收記憶體,但是其他瀏覽器的GC機制都會很好的處理了這個問題。
document.body.appendChild(obj.elementReference);
}
[/javascript]

第三種:將事件處理函式放在定義它的函式的內部。這種情況之前就看到過,回想下自己以前編寫js的方式:外包一個自執行函式,裡面定義閉包內的變數和功能函式,也不乏對事件處理程式的處理。這樣是否會造成IE下的記憶體洩漏呢?下面是兩個測試程式:

[javascript]
var test=function(){
   var div=document.getElementById("test");
   var i=0;
   while((i++) < 20){
     (function(index){
    var o=document.createElement("p");
o.innerHTML="AAA";
o.onclick=function(){
  alert("haha,leap");
}
div.appendChild(o);
o.onclick=null;
div.removeChild(o);
})(i);
   }
}
[/javascript]
[javascript]
function addEvent(){
  var div=document.getElementById("event");
  div.onclick=function(){
     this.parentNode.removeChild(this);
  }
}
[/javascript]

上面的一段程式也是從網上摘錄下來做測試的,在閉包中動態生成一個div元素,並給它新增事件,事件處理程式寫在閉包裡面,也就是內涵在test函式裡面,可是在removeChild的時候,Drip下顯示還是記憶體洩漏了,即使是把它的onclick屬性設定為null也不行。第二個測試程式中,在事件處理程式中通過removeChild刪除當前節點的時候,也顯示記憶體洩漏。

第四種:在建立DOM物件時插入script。這個還是第一次看到。即是通過createElement建立DOM元素的時候,直接在字串中插入了js程式碼:document.createElement(“<div onclick=’foo();’>”),但是這種方式只在IE下有效。通過測試下面的程式,在Drip中也確實顯示記憶體洩漏了

[javascript]
var leakMemory=function(){
   for(i = 0; i < 5000; i++){
     var parentDiv = document.createElement("<div onClick=’foo()’>");
   }
}
[/javascript]

第五種:總是先將新建立的DOM物件插入到文件後,在對其進行其他操作。對於這點,我想象不到它是如何造成記憶體洩漏的。而且,它跟頁面優化的一些方式可能存在衝突。在某些情況下,在建立了DOM元素之後,先處理DOM的操作,最後才插入到文件中,這樣可以避免儘可能的由於reflow影響效能的情況。這可能就需要一個權衡了吧,因地制宜~

總結:

上面是本人通過使用Drip工具測試的結果,但是由於在sIEVE和JS Leaps Detector下測試都沒發現記憶體洩漏的情況,所以糾結的很。經過這一番折騰,也不枉自己一番倒騰倒騰吧,在以後的編寫程式碼中,可以或多或少的去避免這些不必要的可能造成記憶體洩漏的情況出現。

同時,如果有說錯的地方,歡迎指正,共同學習~~

更多參考:《如何防止動態載入JavaScript引起的記憶體洩漏問題》,《javascript 記憶體管理 避免記憶體洩漏》,《關於ie中jscript的記憶體洩漏》,《javascript垃圾回收和IE記憶體洩露》,《防止Javascript造成IE記憶體洩漏的若干原則》,《JScript記憶體洩漏/ie記憶體洩漏》,《關於Javascript的記憶體洩漏問題的整理稿》,《JScript Memory Leaks》,《Understanding and Solving Internet Explorer Leak Patterns》,《理解並解決IE的記憶體洩漏方式[翻譯]

相關文章