我所理解的IE內存泄露
最近在做一些web方面的東西,突然發現IE在removeChild一個元素并釋放了其dom對象的引用之后,在任務管理器中并不能將內存釋放. 這讓我相當郁悶, Google一翻,各種內存泄露的說法,看的我頭暈。最終還是有些收獲。
測試1:
1.不泄露!
function test2(){
gb =
document.getElementById('garbageBin');
var i =
0;
while(i++<10000){
var o =
document.createElement("<div onclick='foo();'>");
gb.appendChild(o);
gb.removeChild(o);
}
}
2 或者不當即removeChild, 而是過后在另外一個函數中 gb.innerHTML=””
也不泄露。也就是,在刷新之前,可以把內存釋放出來。
3 如果即不remove又不 把gb置空, 則刷新也要不回內存.
4. 如果只是var o = document.createElement("<div
onclick='foo();'>"); 其他什么也不做,
同樣導致泄露。
以上測試在IE7。IE6等會再說。 這告訴我們,如果你在創建元素的時候已經關聯了事件處理對象,也就是內聯的foo(), 那么一定要將元素掛載到頁面中的dom樹上, 這樣你才有可能通過remove或者innerHTML=’’的方式回收內存。
我到現在對于內存泄露還有疑惑,到底怎樣才算泄露?
是刷新頁面之前可以把內存收回才算不泄露,還是刷新之后可以收回內存就算不泄露?
前輩們給出的MSDN上那個父子div插入順序的例子, 我在IE7中跑, 似乎不像網上說的那樣,結果是有泄露的那個函數,運行完刷新瀏覽器,內存被回收了。而第二個函數內存一直沒長。 這樣理解的話,應該是瀏覽器刷新之前就被回收(不上漲)就算不泄露吧。那么刷新之后還無法收回的算什么呢?更厲害的內存泄露?呵呵。我暈了。
暫且認為:如果在瀏覽器刷新之前, 內存可以被回收,才算不泄露。
得出:
規則1:創建的元素,如果包含內聯腳本(這是這條規則的前提情況),一定要掛載在dom樹上!不能先掛載到不在樹上的元素!
如果不包含內聯腳本對象, 則不會泄露。
如果包含了內聯腳本,則記得先掛在樹上~ 哈哈
也就是先把父節點掛載,然后掛載子節點到父節點.
再者說來,在FF等其他瀏覽器中,根本不允許
document.createElement("<div
onclick='foo();'>"); 這樣的代碼出現么!
所以按照標準的寫法來做是有好處的!
var o
=document.createElement(“div”)
o.onclick = foo;
這樣就不會有泄露!
規則2
不討論內聯事件對象的情況,因為那是個特例, 來看這樣的情況
function test(){
gb =
document.getElementById('garbageBin');
var i = 0;
while(i++<5000){
(function(){
var o =
document.createElement("div");
//產生循環引用!
o.onclick = function(){
alert('haha')
}
gb.appendChild(o)
//雖然remove了元素,由于循環引用的存在,無法回收內存
//應當在remove之前,打破循環引用!
//o.onclick = null; //break!
gb.removeChild(o)
})();
}
}
注意哈,這此會導致內存泄露,就像前輩們說的那樣, o.onclick = function一句產生了dom元素和function對象的循環引用。就算掛載到dom,然后remove,內存依然不能收回。(似乎能收回一些,但是仍然沒有回到調用前的數值), PF使用率成上升趨勢.
當然解決方法很簡單, 只要在 removeChild 之前將循環打破即可. 使用o.onclick = null 即可,或者事件響應寫在函數外面。這個網上有很多講,不多說。我關注的其實不是這個,主要是, IE7的removeChild到底能做些什么? 刷新頁面的時候,回收的又是哪些內存?
(如果不append也不remove,則會徹底的泄露,刷新無濟于事.)
我將紅色的部分,也就是內部包裝的function去掉。循環5000次,內存沒有增長!
我做這樣的猜測:內存泄露是由于循環引用沒錯,然而onclick 關聯的那個function 屬于匿名函數哈,在整個過程中只解析一次,因此無論你循環多少次,泄露的都只是跟這一個對象有關,因此泄露數量很少很少,以至于看不出。
然而包裝成函數調用5000次呢?就生成了5000個onclick關聯的function, 泄露的內容就增大了5000倍,因此我們可以看的出來。
3
下面測試正常的創建元素。
先測試只創建, 不append到樹上的情況.
//在IE7下沒有問題,內存不會增長,說明IE7可以動態回收不在結點樹上的并且沒有關//聯JS對象, 且沒有其他對象引用它的元素.
function test2(){
var i = 0;
while(i++<5000){
//(function(){
var o =
document.createElement("<div>");
o.innerHTML = "AAA";
//加上下面兩句也沒問題!
buf.push(o);//如果后面不做處理,則不會被回收哦
buf.pop(); //直接pop掉,也就沒有對象引用這個元素了
//})();
//buf.pop 放到這里也完全OK,內存不會增長
}
}
也就是說,定義一個dom元素,只要沒有循環引用, 不append到dom樹上也沒問題。只要沒有被引用到,就會被垃圾回收。
這幾個例子都是創建一個就刪除一個,那么如果先緩存下來,然后再刪除呢?
看這個例子:
var i = 0;
while(i++<5000){
(function(){
var o =
document.createElement("<div>");
o.onclick = foo;
o.innerHTML = "AAA";
buf.push(o);//如果后面不做處理,則不會被回收哦
})();
}
//直接清空buf
buf.length = 0;
/*
//嘗試逐個置空方法
for(var i=0;i<buf.length;i++){
//document.body.removeChild(buf[i]);
buf[i] = null;
}
*/
//嘗試pop
//while(buf.pop());
該例子先將5000個對象存在buf里,然后再嘗試各種方法去釋放他們,也就是斷開對他們的引用。
然而實驗結果是: 占用較大的內存,而且每次調用這個函數,PF使用率都是相同的! 這說明,內存不會累計增加。
然而也不會立即的釋放。雖然PF使用率數值沒變,但是由于再次調用還是占用這些,說明之前占用的內存被新的內容覆蓋了。然而總體情況,還是沒有得到理想的釋放效果。
很讓人郁悶,為什么創建一個就釋放一個就可以,創建多個然后再釋放就不完全了呢?
然而這種情況只是在測試,畢竟沒人會去創建一堆不掛載到頁面dom的元素吧~
再來看掛載到dom樹上的情況:
下面的例子也OK
while(i++<5000){
(function(){
var o =
document.createElement("<div>");
o.onclick = foo; //因為定義在了外部,沒有循環引用問題
o.innerHTML = "AAA";
document.body.appendChild(o);
document.body.removeChild(o);
})();
}
如果去掉removeChild一行,內存情況也尚好,只是增加一點,這是正常的,畢竟顯示到頁面上需要占用內存。
然而讓我不解的是: 為什么append到頁面就占用很少內存,而保存
到buf里面就會占用很大內存呢??不解!
而且,如果先append,然后保存到buf里,占用內存依然很少!
還有如果不設置innerHTML, 則占用內存極少! 里面的原因我就猜不到了.
我只能得出這樣的結論: IE中,創建一個元素,務必把它掛載到dom樹上! 這樣即使不remove,刷新后內存也會釋放。
如果將刪除代碼移到function外面, 情況一樣,沒有泄露。
再來實驗將5000個對象掛載后,然后再刪除的情況
我們通過buf保存對象的引用:
while(i++<10000){
var o;
(function(){
o = document.createElement("<div>");
o.onclick = foo;
o.innerHTML = "haha";
o.style.left="10px";
document.body.appendChild(o);
buf.push(o);//保存引用
})();
}
//
這里調用removeChild來釋放
while(buf.length>0){
document.body.removeChild(buf.pop());
}
測試結果還不錯,雖然內存會上升,但是多次調用會維持在一個水平上,這說明,每次調用時,新的對象占用舊的對象的內存,因此不會累計增加,還算OK.
結論是:同未掛載的情況類似,如果一次性緩存多個對象然后統一removeChild來清除,則IE不會立即釋放內存,如果有新的變量或者對象出現,則會覆蓋那部分內存。總的情況還不差。
使用另一種方式刪除:定義一個看不見的元素gb當做垃圾站
/*
while(buf.length>0){
//首先說明,一個元素只能掛載到一個點上,因為dom是一個樹結構,
//元素對象只是將指針掛載到樹的某個位置,之前對象在body上,現在掛載到了
//gb上,那么body中就不顯示了,而轉到gb上來,
//另外如果將一個元素innerHTML置空,意味著其子元素的內存被釋放!
//所以,這是一個名副其實的回收站~哈哈
gb.appendChild(buf.pop());
gb.innerHTML = "";
}
*/
這種情況得到的結果和上面類似,然而內存占用更少,上面是35M左右,而這個維持在25M左右,情況好了不少。
只是效率低了一點,因為有append操作
其實如果不做清除的時候,內存占用到了25M. 而清除之后,第一種方法35M,第二種方法25M, 似乎根本沒有清除,其實還是這樣,新的內容會覆蓋舊的內存,只是任務管理器沒有顯示出來。因為如果不清除,多次調用后內存是累計增加的,而清除后會維持在一個水平。
還有pop的速度很慢,改成for會快很多!
var l=buf.length;
for(var i=0;i<l;i++){
gb.appendChild(buf[i]);
}
gb.innerHTML = ""; //置空,子元素全被釋放!
buf.length = 0;
//重置buf
所以,使用這種方法清除元素是再好不過的了。 可是需要注意的是,如果內部元素有循環引用的現象,清除之前一定要先把循環引用斷開,方法就是遞歸的清除類型為function的屬性。
只要有善于發現循環引用的良好習慣~ 問題就不是問題了~