魔鬼的夢魘—驗證IE中的js內存泄露模式(續)
前面幾篇文章我們驗證學習了Justin Rogers文章中提出的集中泄露模式,但是其中並沒有介紹Iframe的內存泄露情況;其中的各種原因,我覺的雖然當時ajax的興起,極大地促進了web應用程序的開發和發展,但是並沒有涉及到大規模的動態更新頁面DOM元素,所以這個時候的泄露都不至於會引起大家的關注;但是隨着大量js類庫框架的出現,特別是extjs的出現,功能豐富界面漂亮美觀,同時提供了大量豐富的前端控件,頓時使人眼前一亮,很多人都會不由的感到震撼,原來bs也可以做出這么優美酷炫的界面;特別是最近移動互聯網的風行,很多復雜的應用都轉移到了前段,這些復雜的應用基本上展現基本上都會涉及到動態創建DOM,同時很多應用也會使用iframe,這就會導致內存泄露的滋生;如果你的應用足夠復雜,那么泄露的情況也會十分嚴重,甚至導致瀏覽器崩潰。
我們先來看一下“史前時代”,使用iframe是否存在內存泄露,泄露的情況如何?我們新建一個普通的靜態html頁面;然后我們在一個包含iframe的頁面中,通過點擊按鈕不斷的刷新iframe,具體代碼如下
iframe嵌套的靜態html頁面
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head >
< title ></ title >
</ head >
< body >
UnLeakIFrameContent
</ body >
</ html >
包含iframe的頁面
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head >
< title ></ title >
< script type ="text/javascript" >
var IFrameLeakTester = {
frameTagId: "frame"
,
staticDOMUrl: "UnLeakIFrameContent.htm"
,
staticLeak: function () {
var frame = document.getElementById( this.frameTagId);
frame.src = this.staticDOMUrl;
}
};
</ script >
</ head >
< body >
< div >
< input type ="button" value = "staticDOMLeak" onclick ="IFrameLeakTester.staticLeak();" />
</ div >
< div >
< iframe id ="frame" src ="UnLeakIFrameContent.htm" height = "500px" width = "100%" >
</ iframe >
</ div >
</ body >
</ html >
從代碼中,我們可以看到沒有任何的循環引用和動態的DOM創建,自然我們刷新整個包含iframe的頁面也不會有內存泄露,使用sIEve測試也證明了這一點。
圖 1. 刷新iframe中嵌套靜態網頁內存泄露情況
如果我們不斷的點擊頁面中的按鈕,來不斷的刷新iframe,這個時候是否有內存泄露呢?從下圖的測試數據來看,我們看到泄露的確是存在的,只是泄露的數目並沒有隨刷新的次數遞增,所以我猜想是IE緩存了iframe的頁面對象的DOM。
圖 2. Iframe嵌套靜態頁面,不斷刷新iframe的內存泄露情況
那么如果iframe嵌套的頁面有動態創建DOM的邏輯的話,那泄露的情況會怎樣呢?這次嵌套的頁面我們使用extjs中的那個模仿windows桌面的頁面(examples/desktop/desktop.html),我們先來看一下這個頁面本身是否有泄露問題,從圖3中,我們可以看到是沒有泄露問題的。
圖 3. 直接刷新extjs的desktop例子泄露情況
接下來我們將iframe的嵌套頁面改成extjs的desktop.html,通過同樣的方式,我們看一下刷新整個頁面和不斷刷新iframe時,來看一下內存泄露的具體情況;詳細代碼如下
包含iframe的頁面
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< head >
< title ></ title >
< script type ="text/javascript" >
var IFrameLeakTester = {
frameTagId: "frame"
,
dynamicDOMUrl: "http://localhost:30000/examples/desktop/desktop.html"
,
leak: function () {
this._leak( true);
}
,
unLeak: function () {
this._leak( false);
}
,
_leak: function (leak) {
var frame = document.getElementById( this.frameTagId);
// 網上流傳的方法,但是在這種情況下沒有什么效果
if (!leak) {
this.clear(frame);
}
frame.src = this.dynamicDOMUrl;
}
,
clear: function (frame) {
if (frame) {
frame.contentWindow.document.write("");
frame.contentWindow.document.clear();
top.CollectGarbage();
}
}
};
</ script >
</ head >
< body >
< div >
< input type ="button" value ="dynamicDOMLeak" onclick ="IFrameLeakTester.leak();" />
< input type ="button" value = "dynamicDOMUnLeak" onclick ="IFrameLeakTester.unLeak();" />
</ div >
< div >
< iframe id ="frame" src ="UnLeakIFrameContent.htm" height = "500px" width = "100%" >
</ iframe >
</ div >
</ body >
</ html >
圖 4. 刷新嵌套extjs例子的這個頁面內存泄露情況
圖 5. 不斷刷新嵌套extjs例子的iframe內存泄露情況
從圖4和圖5中,我們可以看到刷新整個頁面也是沒有泄露的,但是刷新iframe卻存在泄露,並且刷新的次數越多泄露越嚴重。綜合前面iframe嵌套靜態頁面的例子,我們可以知道,IE可以緩存頁面本身的DOM,以備再次刷新iframe的同一頁面的時候用;但是如果iframe嵌套的頁面中新建了DOM元素,那么每次打開這個頁面的時候都會新建對應的DOM元素,從而導致嚴重的內存泄露。
對於怎樣應對這種內存泄露的情況,我沒有發現行之有效的方法;在網上搜到一些解決的方法,但是我測試了一下(單擊dynamicDOMUnLeak按鈕),沒有什么效果,具體情況如下圖
圖 6. 解決泄露的代碼沒有效果