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