javascript
JavaScript内存释放
在IE下的JS編程中,以下的編程方式都會(huì)造成即使關(guān)閉IE也無法釋放內(nèi)存的問題,下面分類給出:
1、給DOM對(duì)象添加的屬性是一個(gè)對(duì)象的引用。范例:
var MyObject = {};
document.getElementById('myDiv').myProp = MyObject;
解決方法:
在window.onunload事件中寫上: document.getElementById('myDiv').myProp = null;
2、DOM對(duì)象與JS對(duì)象相互引用。范例:
function Encapsulator(element) {
? this.elementReference = element;
? element.myProp = this;
}
new? Encapsulator(document.getElementById('myDiv'));
解決方法:
在onunload事件中寫上: document.getElementById('myDiv').myProp = null;
3、給DOM對(duì)象用attachEvent綁定事件。范例:
function doClick() {}
element.attachEvent("onclick", doClick);
解決方法:
在onunload事件中寫上: element.detachEvent('onclick', doClick);
4、從外到內(nèi)執(zhí)行appendChild。這時(shí)即使調(diào)用removeChild也無法釋放。范例:
var parentDiv =? document.createElement("div");
var childDiv = document.createElement("div");
document.body.appendChild(parentDiv);
parentDiv.appendChild(childDiv);
解決方法:
從內(nèi)到外執(zhí)行appendChild:
var parentDiv =? document.createElement("div");
var childDiv = document.createElement("div");
parentDiv.appendChild(childDiv);
document.body.appendChild(parentDiv);
5、反復(fù)重寫同一個(gè)屬性會(huì)造成內(nèi)存大量占用(但關(guān)閉IE后內(nèi)存會(huì)被釋放)。范例:
for(i = 0; i < 5000; i++) {
? hostElement.text = "asdfasdfasdf";
}
這種方式相當(dāng)于定義了5000個(gè)屬性!
解決方法:
其實(shí)沒什么解決方法:P~~~就是編程的時(shí)候盡量避免出現(xiàn)這種情況咯~~
說明:
1、以上資料均來源于微軟官方的MSDN站點(diǎn),鏈接地址:
http://msdn.microsoft.com/librar ... e_leak_patterns.asp
大家可以到上面這個(gè)地址中看到詳細(xì)的說明,包括范例和圖例都有。只是我英文不太好,看不太懂,如果我上述有失誤或有需要補(bǔ)充的地方請(qǐng)大家指出。
2、對(duì)于第一條,事實(shí)上包括 element.onclick = funcRef 這種寫法也算在其中,因?yàn)檫@也是一個(gè)對(duì)對(duì)象的引用。在頁(yè)面onunload時(shí)應(yīng)該釋放掉。
3、對(duì)于第三條,在MSDN的英文說明中好像是說即使調(diào)用detachEvent也無法釋放內(nèi)存,因?yàn)樵赼ttachEvent的時(shí)候就已經(jīng)造成內(nèi)存“LEAK”了,不過detachEvent后情況還是會(huì)好一點(diǎn)。不知道是不是這樣,請(qǐng)英文好的親能夠指出。
4、在實(shí)際編程中,這些內(nèi)存問題的實(shí)際影響并不大,尤其是給客戶使用時(shí),客戶對(duì)此絕不會(huì)有察覺,然而這些問題對(duì)于程序員來說卻始終是個(gè)心病 --- 有這樣的BUG心里總會(huì)覺得不舒服吧?能解決則給與解決,這樣是最好的。事實(shí)上我在webfx.eae.net這樣頂級(jí)的JS源碼站點(diǎn)中,在它們的源碼里都會(huì)看到采用上述解決方式進(jìn)行內(nèi)存的釋放管理。
================================================================================================
Post: Rimifon
理解并解決IE的內(nèi)存泄漏方式
Web開發(fā)的發(fā)展
??? 在過去一些的時(shí)候,Web開發(fā)人員并沒有太多的去關(guān)注內(nèi)存泄露問題。那時(shí)的頁(yè)面間聯(lián)系大都比較簡(jiǎn)單,并主要使用不同的連接地址在同一
個(gè)站點(diǎn)中導(dǎo)航,這樣的設(shè)計(jì)方式是非常有利于瀏覽器釋放資源的。即使Web頁(yè)面運(yùn)行中真的出現(xiàn)了資源泄漏,那它的影響也是非常有限而且常常
是不會(huì)被人在意的。
今天人們對(duì)Web應(yīng)用有了高更的要求。一個(gè)頁(yè)面很可能數(shù)小時(shí)不會(huì)發(fā)生URL跳轉(zhuǎn),并同時(shí)通過Web服務(wù)動(dòng)態(tài)的更新頁(yè)面內(nèi)容。復(fù)雜的事件關(guān)聯(lián)
設(shè)計(jì)、基于對(duì)象的JScript和DHTML技術(shù)的廣泛采用,使得代碼的能力達(dá)到了其承受的極限。在這樣的情況和改變下,弄清楚內(nèi)存泄露方式變得
非常的急迫,特別是過去這些問題都被傳統(tǒng)的頁(yè)面導(dǎo)航方法給屏蔽了。
還算好的事情是,當(dāng)你明確了希望尋找什么時(shí),內(nèi)存泄露方式是比較容易被確定的。大多數(shù)你能遇到的泄露問題我們都已經(jīng)知道,你只需
要少量額外的工作就會(huì)給你帶來好處。雖然在一些頁(yè)面中少量的小泄漏問題仍會(huì)發(fā)生,但是主要的問題還是很容易解決的。
泄露方式
在接下來的內(nèi)容中,我們會(huì)討論內(nèi)存泄露方式,并為每種方式給出示例。其中一個(gè)重要的示例是JScript中的Closure技術(shù),另一個(gè)示例是
在事件執(zhí)行中使用Closures。當(dāng)你熟悉本示例后,你就能找出并修改你已有的大多數(shù)內(nèi)存泄漏問題,但是其它Closure相關(guān)的問題可能又會(huì)被忽
視。
現(xiàn)在讓我們來看看這些個(gè)方式都有什么:
1、循環(huán)引用(Circular References) — IE瀏覽器的COM組件產(chǎn)生的對(duì)象實(shí)例和網(wǎng)頁(yè)腳本引擎產(chǎn)生的對(duì)象實(shí)例相互引用,就會(huì)造成內(nèi)存泄漏。
這也是Web頁(yè)面中我們遇到的最常見和主要的泄漏方式;
2、內(nèi)部函數(shù)引用(Closures) — Closures可以看成是目前引起大量問題的循環(huán)應(yīng)用的一種特殊形式。由于依賴指定的關(guān)鍵字和語(yǔ)法結(jié)構(gòu),
Closures調(diào)用是比較容易被我們發(fā)現(xiàn)的;
3、頁(yè)面交叉泄漏(Cross-Page Leaks) — 頁(yè)面交叉泄漏其實(shí)是一種較小的泄漏,它通常在你瀏覽過程中,由于內(nèi)部對(duì)象薄計(jì)引起。下面我們
會(huì)討論DOM插入順序的問題,在那個(gè)示例中你會(huì)發(fā)現(xiàn)只需要改動(dòng)少量的代碼,我們就可以避免對(duì)象薄計(jì)對(duì)對(duì)象構(gòu)建帶來的影響;
4、貌似泄漏(Pseudo-Leaks) — 這個(gè)不是真正的意義上的泄漏,不過如果你不了解它,你可能會(huì)在你的可用內(nèi)存資源變得越來越少的時(shí)候極
度郁悶。為了演示這個(gè)問題,我們將通過重寫Script元素中的內(nèi)容來引發(fā)大量?jī)?nèi)存的"泄漏"。
循環(huán)引用
循環(huán)引用基本上是所有泄漏的始作俑者。通常情況下,腳本引擎通過垃圾收集器(GC)來處理循環(huán)引用,但是某些未知因數(shù)可能會(huì)妨礙從其
環(huán)境中釋放資源。對(duì)于IE來說,某些DOM對(duì)象實(shí)例的狀態(tài)是腳本無法得知的。下面是它們的基本原則:
??? Figure 1: 基本的循環(huán)引用模型
??? 本模型中引起的泄漏問題基于COM的引用計(jì)數(shù)。腳本引擎對(duì)象會(huì)維持對(duì)DOM對(duì)象的引用,并在清理和釋放DOM對(duì)象指針前等待所有引用的移除
。在我們的示例中,我們的腳本引擎對(duì)象上有兩個(gè)引用:腳本引擎作用域和DOM對(duì)象的expando屬性。當(dāng)終止腳本引擎時(shí)第一個(gè)引用會(huì)釋放,DOM
對(duì)象引用由于在等待腳本擎的釋放而并不會(huì)被釋放。你可能會(huì)認(rèn)為檢測(cè)并修復(fù)假設(shè)的這類問題會(huì)非常的容易,但事實(shí)上這樣基本的的示例只是
冰山一角。你可能會(huì)在30個(gè)對(duì)象鏈的末尾發(fā)生循環(huán)引用,這樣的問題排查起來將會(huì)是一場(chǎng)噩夢(mèng)。
? 如果你仍不清楚這種泄漏方式在HTML代碼里到底怎樣,你可以通過一個(gè)全局腳本變量和一個(gè)DOM對(duì)象來引發(fā)并展現(xiàn)它。
<html>
<head>
<script language="JScript">
var myGlobalObject;
function SetupLeak()
{
// First set up the script scope to element reference
myGlobalObject = document.getElementById("LeakedDiv");
// Next set up the element to script scope reference
document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;
}
function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty = null;
}
</script>
</head>
<body οnlοad="SetupLeak()" οnunlοad="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
你可以使用直接賦null值得方式來破壞該泄漏情形。在頁(yè)面文檔卸載前賦null值,將會(huì)讓腳本引擎知道對(duì)象間的引用鏈沒
有了。現(xiàn)在它將能正常的清理引用并釋放DOM對(duì)象。在這個(gè)示例中,作為Web開發(fā)員的你因該更多的了解了對(duì)象間的關(guān)系。
作為一個(gè)基本的情形,循環(huán)引用可能還有更多不同的復(fù)雜表現(xiàn)。對(duì)基于對(duì)象的JScript,一個(gè)通常用法是通過封裝JScript對(duì)象來擴(kuò)充DOM對(duì)
象。在構(gòu)建過程中,你常常會(huì)把DOM對(duì)象的引用放入JScript對(duì)象中,同時(shí)在DOM對(duì)象中也存放上對(duì)新近創(chuàng)建的JScript對(duì)象的引用。你的這種應(yīng)
用模式將非常便于兩個(gè)對(duì)象之間的相互訪問。這是一個(gè)非常直接的循環(huán)引用問題,但是由于使用不用的語(yǔ)法形式可能并不會(huì)讓你在意。要破環(huán)
這種使用情景可能變得更加復(fù)雜,當(dāng)然你同樣可以使用簡(jiǎn)單的示例以便于清楚的討論。
<html>
<head>
<script language="JScript">
function Encapsulator(element)
{
// Set up our element
this.elementReference = element;
// Make our circular reference
element.expandoProperty = this;
}
function SetupLeak()
{
// The leak happens all at once
new Encapsulator(document.getElementById("LeakedDiv"));
}
function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty = null;
}
</script>
</head>
<body οnlοad="SetupLeak()" οnunlοad="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
更復(fù)雜的辦法還有記錄所有需要解除引用的對(duì)象和屬性,然后在Web文檔卸載的時(shí)候統(tǒng)一清理,但大多數(shù)時(shí)候你可能會(huì)再造
成額外的泄漏情形,而并沒有解決你的問題。
閉包函數(shù)(Closures)
由于閉包函數(shù)會(huì)使程序員在不知不覺中創(chuàng)建出循環(huán)引用,所以它對(duì)資源泄漏常常有著不可推卸的責(zé)任。而在閉包函數(shù)自己被釋放前,我們很難判斷父函數(shù)的參數(shù)以及它的局部變量是否能被釋放。實(shí)際上閉包函數(shù)的使用已經(jīng)很普通,以致人們頻繁的遇到這類問題時(shí)我們卻束手無策。在詳細(xì)了解了閉包背后的問題和一些特殊的閉包泄漏示例后,我們將結(jié)合循環(huán)引用的圖示找到閉包的所在,并找出這些不受歡迎的引用來至何處。
Figure 2. 閉包函數(shù)引起的循環(huán)引用
??? 普通的循環(huán)引用,是兩個(gè)不可探知的對(duì)象相互引用造成的,但是閉包卻不同。代替直接造成引用,閉包函數(shù)則取而代之從其父函數(shù)作用域中引入信息。通常,函數(shù)的局部變量和參數(shù)只能在該被調(diào)函數(shù)自身的生命周期里使用。當(dāng)存在閉包函數(shù)后,這些變量和參數(shù)的引用會(huì)和閉包函數(shù)一起存在,但由于閉包函數(shù)可以超越其父函數(shù)的生命周期而存在,所以父函數(shù)中的局部變量和參數(shù)也仍然能被訪問。在下面的示例中,參數(shù)1將在函數(shù)調(diào)用終止時(shí)正常被釋放。當(dāng)我們加入了一個(gè)閉包函數(shù)后,一個(gè)額外的引用產(chǎn)生,并且這個(gè)引用在閉包函數(shù)釋放前都不會(huì)被釋放。如果你碰巧將閉包函數(shù)放入了事件之中,那么你不得不手動(dòng)從那個(gè)事件中將其移出。如果你把閉包函數(shù)作為了一個(gè)expando屬性,那么你也需要通過置null將其清除。
同時(shí)閉包會(huì)在每次調(diào)用中創(chuàng)建,也就是說當(dāng)你調(diào)用包含閉包的函數(shù)兩次,你將得到兩個(gè)獨(dú)立的閉包,而且每個(gè)閉包都分別擁有對(duì)參數(shù)的引用。由于這些顯而易見的因素,閉包確實(shí)非常用以帶來泄漏。下面的示例將展示使用閉包的主要泄漏因素:
<html>
<head>
<script language="JScript">
function AttachEvents(element)
{
// This structure causes element to ref ClickEventHandler
element.attachEvent("onclick", ClickEventHandler);
function ClickEventHandler()
{
// This closure refs element
}
}
function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}
function BreakLeak()
{
}
</script>
</head>
<body οnlοad="SetupLeak()" οnunlοad="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
如果你對(duì)怎么避免這類泄漏感到疑惑,我將告訴你處理它并不像處理普通循環(huán)引用那么簡(jiǎn)單。"閉包"被看作函數(shù)作用域中的一個(gè)臨時(shí)對(duì)象。一旦函數(shù)執(zhí)行退出,你將失去對(duì)閉包本身的引用,那么你將怎樣去調(diào)用detachEvent方法來清除引用呢?在Scott Isaacs的MSN Spaces上有一種解決這個(gè)問題的有趣方法。這個(gè)方法使用一個(gè)額外的引用(原文叫second closure,可是這個(gè)示例里致始致終只有一個(gè)closure)協(xié)助window對(duì)象執(zhí)行onUnload事件,由于這個(gè)額外的引用和閉包的引用存在于同一個(gè)對(duì)象域中,于是我們可以借助它來釋放事件引用,從而完成引用移除。為了簡(jiǎn)單起見我們將閉包的引用暫存在一個(gè)expando屬性中,下面的示例將向你演示釋放事件引用和清除expando屬性。
<html>
<head>
<script language="JScript">
function AttachEvents(element)
{
// In order to remove this we need to put
// it somewhere. Creates another ref
element.expandoClick = ClickEventHandler;
// This structure causes element to ref ClickEventHandler
element.attachEvent("onclick", element.expandoClick);
function ClickEventHandler()
{
// This closure refs element
}
}
function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}
function BreakLeak()
{
document.getElementById("LeakedDiv").detachEvent("onclick",
document.getElementById("LeakedDiv").expandoClick);
document.getElementById("LeakedDiv").expandoClick = null;
}
</script>
</head>
<body οnlοad="SetupLeak()" οnunlοad="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
在這篇KB文章中,實(shí)際上建議我們除非迫不得已盡量不要?jiǎng)?chuàng)建使用閉包。文章中的示例,給我們演示了非閉包的事件引用方式,即把閉包函數(shù)放到頁(yè)面的全局作用域中。當(dāng)閉包函數(shù)成為普通函數(shù)后,它將不再繼承其父函數(shù)的參數(shù)和局部變量,所以我們也就不用擔(dān)心基于閉包的循環(huán)引用了。在非必要的時(shí)候不使用閉包這樣的編程方式可以盡量使我們的代碼避免這樣的問題。
? 最后,腳本引擎開發(fā)組的Eric Lippert,給我們帶來了一篇關(guān)于閉包使用通俗易懂的好文章。他的最終建議也是希望在真正必要的時(shí)候才使用閉包函數(shù)。雖然他的文章沒有提及閉包會(huì)使用的真正場(chǎng)景,但是這兒已有的大量示例非常有助于大家起步。
頁(yè)面交叉泄漏(Cross-Page Leaks)
這種基于插入順序而常常引起的泄漏問題,主要是由于對(duì)象創(chuàng)建過程中的臨時(shí)對(duì)象未能被及時(shí)清理和釋放造成的。它一般在動(dòng)態(tài)創(chuàng)建頁(yè)面元素,并將其添加到頁(yè)面DOM中時(shí)發(fā)生。一個(gè)最簡(jiǎn)單的示例場(chǎng)景是我們動(dòng)態(tài)創(chuàng)建兩個(gè)對(duì)象,并創(chuàng)建一個(gè)子元素和父元素間的臨時(shí)域(譯者注:這里的域(Scope)應(yīng)該是指管理元素之間層次結(jié)構(gòu)關(guān)系的對(duì)象)。然后,當(dāng)你將這兩個(gè)父子結(jié)構(gòu)元素構(gòu)成的的樹添加到頁(yè)面DOM樹中時(shí),這兩個(gè)元素將會(huì)繼承頁(yè)面DOM中的層次管理域?qū)ο?#xff0c;并泄漏之前創(chuàng)建的那個(gè)臨時(shí)域?qū)ο蟆O旅娴膱D示示例了兩種動(dòng)態(tài)創(chuàng)建并添加元素到頁(yè)面DOM中的方法。在第一種方法中,我們將每個(gè)子元素添加到它的直接父元素中,最后再將創(chuàng)建好的整棵子樹添加到頁(yè)面DOM中。當(dāng)一些相關(guān)條件合適時(shí),這種方法將會(huì)由于臨時(shí)對(duì)象問題引起泄漏。在第二種方法中,我們自頂向下創(chuàng)建動(dòng)態(tài)元素,并使它們被創(chuàng)建后立即加入到頁(yè)面DOM結(jié)構(gòu)中去。由于每個(gè)被加入的元素繼承了頁(yè)面DOM中的結(jié)構(gòu)域?qū)ο?#xff0c;我們不需要?jiǎng)?chuàng)建任何的臨時(shí)域。這是避免潛在內(nèi)存泄漏發(fā)生的好方法。
Figure 3. DOM插入順序泄漏模型
??? 接下來,我們將給出一個(gè)躲避了大多數(shù)泄漏檢測(cè)算法的泄漏示例。因?yàn)槲覀儗?shí)際上沒有泄漏任何可見的元素,并且由于被泄漏的對(duì)象太小從而你可能根本不會(huì)注意這個(gè)問題。為了使我們的示例產(chǎn)生泄漏,在動(dòng)態(tài)創(chuàng)建的元素結(jié)構(gòu)中將不得不內(nèi)聯(lián)的包含一個(gè)腳本函數(shù)指針。在我們?cè)O(shè)置好這些元素間的相互隸屬關(guān)系后這將會(huì)使我們泄漏內(nèi)部臨時(shí)腳本對(duì)象。由于這個(gè)泄漏很小,我們不得不將示例執(zhí)行成千上萬次。事實(shí)上,一個(gè)對(duì)象的泄漏只有很少的字節(jié)。在運(yùn)行示例并將瀏覽器導(dǎo)航到一個(gè)空白頁(yè)面,你將會(huì)看到兩個(gè)版本代碼在內(nèi)存使用上的區(qū)別。當(dāng)我們使用第一種方法,將子元素加入其父元素再將構(gòu)成的子樹加入頁(yè)面DOM,我們的內(nèi)存使用量會(huì)有微小的上升。這就是一個(gè)交叉導(dǎo)航泄漏,只有當(dāng)我們重新啟動(dòng)IE進(jìn)程這些泄漏的內(nèi)存才會(huì)被釋放。如果你使用第二種方法將父元素加入頁(yè)面DOM再將子元素加入其父元素中,同樣運(yùn)行若干次后,你的內(nèi)存使用量將不會(huì)再上升,這時(shí)你會(huì)發(fā)現(xiàn)你已經(jīng)修復(fù)了交叉導(dǎo)航泄漏的問題。
<html>
<head>
<script language="JScript">
function LeakMemory()
{
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++)
{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");
// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
function CleanMemory()
{
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++)
{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");
// Changing the order is important, this won't leak
hostElement.appendChild(parentDiv);
parentDiv.appendChild(childDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
</script>
</head>
<body>
<button οnclick="LeakMemory()">Memory Leaking Insert</button>
<button οnclick="CleanMemory()">Clean Insert</button>
<div id="hostElement"></div>
</body>
</html>
這類泄漏應(yīng)該被澄清,因?yàn)檫@個(gè)解決方法有悖于我們?cè)贗E中的一些有益經(jīng)驗(yàn)。創(chuàng)建帶有腳本對(duì)象的DOM元素,以及它們已進(jìn)行的相互關(guān)聯(lián)是了解這個(gè)泄漏的關(guān)鍵點(diǎn)。這實(shí)際上這對(duì)于泄漏來說是至關(guān)重要的,因?yàn)槿绻覀儎?chuàng)建的DOM元素不包含任何的腳本對(duì)象,同時(shí)使用相同的方式將它們進(jìn)行關(guān)聯(lián),我們是不會(huì)有任何泄漏問題的。示例中給出的第二種技巧對(duì)于關(guān)聯(lián)大的子樹結(jié)構(gòu)可能更有效(由于在那個(gè)示例中我們一共只有兩個(gè)元素,所以建立一個(gè)和頁(yè)面DOM不相關(guān)的樹結(jié)構(gòu)并不會(huì)有什么效率問題)。第二個(gè)技巧是在創(chuàng)建元素的開始不關(guān)聯(lián)任何的腳本對(duì)象,所以你可以安全的創(chuàng)建子樹。當(dāng)你把你的子樹關(guān)聯(lián)到頁(yè)面DOM上后,再繼續(xù)處理你需要的腳本事件。牢記并遵守關(guān)于循環(huán)引用和閉包函數(shù)的使用規(guī)則,你不會(huì)再在掛接事件時(shí)在你的代碼中遇到不同的泄漏。
我真的要指出這個(gè)問題,因?yàn)槲覀兛梢钥闯霾皇撬械膬?nèi)存泄漏都是可以很容易發(fā)現(xiàn)的。它們可能都是些微不足道的問題,但往往需要成千上萬次的執(zhí)行一個(gè)更小的泄漏場(chǎng)景才能使問題顯現(xiàn)出來,就像DOM元素插入順序引起的問題那樣。如果你覺得使用所謂的"最佳"經(jīng)驗(yàn)來編程,那么你就可以高枕無憂,但是這個(gè)示例讓我們看到,即使是"最佳"經(jīng)驗(yàn)似乎也可能帶來泄漏。我們這里的解決方案希望能提高這些已有的好經(jīng)驗(yàn),或者介紹一些新經(jīng)驗(yàn)使我們避免泄漏發(fā)生的可能。
貌似泄漏(Pseudo-Leaks)
??? 在大多數(shù)時(shí)候,一些APIs的實(shí)際的行為和它們預(yù)期的行為可能會(huì)導(dǎo)致你錯(cuò)誤的判斷內(nèi)存泄漏。貌似泄漏大多數(shù)時(shí)候總是出現(xiàn)在同一個(gè)頁(yè)面的動(dòng)態(tài)腳本操作中,而在從一個(gè)頁(yè)面跳轉(zhuǎn)到空白頁(yè)面的時(shí)候發(fā)生是非常少見的。那你怎么能象排除頁(yè)面間泄漏那樣來排除這個(gè)問題,并且在新任務(wù)運(yùn)行中的內(nèi)存使用量是否是你所期望的。我們將使用腳本文本的重寫來作為一個(gè)貌似泄漏的示例。
象DOM插入順序問題那樣,這個(gè)問題也需要依賴創(chuàng)建臨時(shí)對(duì)象來產(chǎn)生"泄漏"。對(duì)一個(gè)腳本元素對(duì)象內(nèi)部的腳本文本一而再再而三的反復(fù)重寫,慢慢地你將開始泄漏各種已關(guān)聯(lián)到被覆蓋內(nèi)容中的腳本引擎對(duì)象。特別地,和腳本調(diào)試有關(guān)的對(duì)象被作為完全的代碼對(duì)象形式保留了下來。
<html>
<head>
<script language="JScript">
function LeakMemory()
{
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++)
{
hostElement.text = "function foo() { }";
}
}
</script>
</head>
<body>
<button οnclick="LeakMemory()">Memory Leaking Insert</button>
<script id="hostElement">function foo() { }</script>
</body>
</html>
如果你運(yùn)行上面的示例代碼并使用任務(wù)管理器查看,當(dāng)從"泄漏"頁(yè)面跳轉(zhuǎn)到空白頁(yè)面時(shí),你并不會(huì)注意到任何腳本泄漏。因?yàn)檫@種腳本泄漏完全發(fā)生在頁(yè)面內(nèi)部,而且當(dāng)你離開該頁(yè)面時(shí)被使用的內(nèi)存就會(huì)回收。對(duì)于我們?cè)舅谕男袨閬碚f這樣的情況是糟糕的。你希望當(dāng)重寫了腳本內(nèi)容后,原來的腳本對(duì)象就應(yīng)該徹底的從頁(yè)面中消失。但事實(shí)上,由于被覆蓋的腳本對(duì)象可能已用作事件處理函數(shù),并且還可能有一些未被清除的引用計(jì)數(shù)。正如你所看到的,這就是貌似泄漏。在表面上內(nèi)存消耗量可能看起來非常的糟糕,但是這個(gè)原因是完全可以接受的。
總結(jié)
每一位Web開發(fā)員可能都整理有一份自己的代碼示例列表,當(dāng)他們?cè)诖a中看到如列表中的代碼時(shí),他們會(huì)意識(shí)到泄漏的存在并會(huì)使用一些開發(fā)技巧來避免這些問題。這樣的方法雖然簡(jiǎn)單便捷,但這也是今天Web頁(yè)面內(nèi)存泄漏普遍存在的原因。考慮我們所討論的泄漏情景而不是關(guān)注獨(dú)立的代碼示例,你將會(huì)使用更加有效的策略來解決泄漏問題。這樣的觀念將使你在設(shè)計(jì)階段就把問題估計(jì)到,并且確保你有計(jì)劃來處理潛在的泄漏問題。使用編寫加固代碼(譯者注:就是異常處理或清理對(duì)象等的代碼)的習(xí)慣并且采取清理所有自己占用內(nèi)存的方法。雖然對(duì)這個(gè)問題來說可能太夸張了,你也可能幾乎從沒有見到編寫腳本卻需要自己清理自己占用的內(nèi)存的情況;使這個(gè)問題變得越來越顯著的是,腳本變量和expando屬性間存在的潛在泄漏可能。
如果對(duì)模式和設(shè)計(jì)感興趣,我強(qiáng)烈推薦Scott的這篇blog,因?yàn)槠渲醒菔玖艘粋€(gè)通用的移除基于閉包泄漏的示例代碼。當(dāng)然這需要我們使用更多的代碼,但是這個(gè)實(shí)踐是有效的,并且改進(jìn)的場(chǎng)景非常容易在代碼中定位并進(jìn)行調(diào)試。類似的注入設(shè)計(jì)也可以用在基于expando屬性引起的循環(huán)引用中,不過需要注意所注冊(cè)的方法自身不要讓泄漏(特別使用閉包的地方)跑掉。
總結(jié)
以上是生活随笔為你收集整理的JavaScript内存释放的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无题三首之三
- 下一篇: 写 飞秋 程序,就是把简单的事情重复的做