JavaScript
是一種垃圾收集式語言,這就是說,記憶體是根據對象的建立分配給該對象的,並會在沒有對該對象的引用時由瀏覽器收回。JavaScript
的垃圾收集機制本身並沒有問題,但瀏覽器在為 DOM 對象分配和恢複記憶體的方式上卻有些出入。
Internet Explorer 和 Mozilla Firefox 均使用引用計數來為 DOM
對象處理記憶體。在引用計數系統,每個所引用的對象都會保留一個計數,以獲悉有多少對象正在引用它。如果計數為零,該對象就會被銷毀,其佔用的記憶體也會返回
給堆。雖然這種解決方案總的來說還算有效,但在循環參考方面卻存在一些盲點。
循環參考的問題何在?
當兩個對象互相引用時,就構成了循環參考,其中每個對象的引用計數值都被賦
1。在純垃圾收集系統中,循環參考問題不大:若涉及到的兩個對象中的一個對象被任何其他對象引用,那麼這兩個對象都將被垃圾收集。而在引用計數系統,這兩
個對象都不能被銷毀,原因是引用計數永遠不能為零。在同時使用了垃圾收集和引用計數的混合系統中,將會發生泄漏,因為系統不能正確識別循環參考。在這種情
況下,DOM 對象和 JavaScript 對象均不能被銷毀。清單 1 顯示了在 JavaScript 對象和 DOM
對象間存在的一個循環參考。
清單 1. 循環參考導致了記憶體流失
<html><br /> <body><br /> <mce:script type="text/javascript"><!--<br /> document.write("circular references between JavaScript and DOM!");<br /> var obj;<br /> window.onload = function(){<br /> obj=document.getElementById("DivElement");<br /> document.getElementById("DivElement").expandoProperty=obj;<br /> obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));<br /> };</p><p>// --></mce:script><br /> <div id="DivElement">Div Element</div><br /> </body><br /></html>
如上述清單中所示,JavaScript 對象 obj
擁有到 DOM 對象的引用,表示為 DivElement
。而 DOM 對象則有到此 JavaScript 對象的引用,由 expandoProperty
表示。可見,JavaScript 對象和 DOM 對象間就產生了一個循環參考。由於 DOM
對象是通過引用計數管理的,所以兩個對象將都不能銷毀。
另一種記憶體流失模式
在清單 2 中,通過調用外部函數 myFunction
建立循環參考。同樣,JavaScript 對象和 DOM 對象間的循環參考也會導致記憶體流失。
清單 2. 由外部函數調用引起的記憶體流失
<html><br /><head><br /><mce:script type="text/javascript"><!--<br />document.write(" object s between JavaScript and DOM!");<br />function myFunction(element){<br />this.elementReference = element;<br />// This code forms a circular reference here<br />//by DOM-->JS-->DOM<br />element.expandoProperty = this;<br />}<br />function Leak() {<br />//This code will leak<br />new myFunction(document.getElementById("myDiv"));<br />}</p><p>// --></mce:script><br /></head><br /><body onload="Leak()"><br /><div id="myDiv"></div><br /></body><br /></html>
正如這兩個程式碼範例所示,循環參考很容易建立。在 JavaScript 最為方便的編程結構之一:閉包中,循環參考尤其突出。
JavaScript 中的閉包
JavaScript 的過人之處在於它允許函數嵌套。一個嵌套的內建函式可以繼承外部函數的參數和變數,並由該外部函數私人。清單 3
顯示了內建函式的一個樣本。
清單 3. 一個內建函式
function parentFunction(paramA)<br />{<br /> var a = paramA;<br /> function childFunction()<br /> {<br />return a + 2;<br /> }<br /> return childFunction();<br />}
JavaScript 開發人員使用內建函式來在其他函數中整合小型的實用函數。如清單 3 所示,此內建函式 childFunction
可以訪問外部函數 parentFunction
的變數。當內建函式獲得和使用其外部函數的變數時,就稱其為一個閉包
。
瞭解閉包
考慮如清單 4 所示的程式碼片段。
清單 4. 一個簡單的閉包
<html><br /><body><br /><mce:script type="text/javascript"><!--<br />document.write("Closure Demo!!");<br />window.onload=<br />function closureDemoParentFunction(paramA)<br />{<br /> var a = paramA;<br /> return function closureDemoInnerFunction (paramB)<br /> {<br /> alert( a +" "+ paramB);<br /> };<br />};<br />var x = closureDemoParentFunction("outer x");<br />x("inner x");</p><p>// --></mce:script><br /></body><br /></html>
在上述清單中,closureDemoInnerFunction
是在父函數 closureDemoParentFunction
中定義的內建函式。當用外部的 x
對 closureDemoParentFunction
進行調用時,外部函數變數 a
就會被賦值為外部的 x
。函數會返回指向內建函式 closureDemoInnerFunction
的指標,該指標包括在變數 x
內。
外部函數 closureDemoParentFunction
的本地變數 a
即使在外部函數返回時仍會存在。這一點不同於 C/C++ 這樣的程式設計語言,在 C/C++ 中,一旦函數返回,本地變數也將不複存在。在
JavaScript 中,在調用 closureDemoParentFunction
的時候,帶有屬性 a
的範圍對象將會被建立。該屬性包括值 paramA
,又稱為“外部 x”
。同樣地,當 closureDemoParentFunction
返回時,它將會返回內建函式 closureDemoInnerFunction
,該函數包括在變數 x
中。
由於內建函式持有到外部函數的變數的引用,所以這個帶屬性 a
的範圍對象將不會被垃圾收集。當對具有參數值 inner x
的 x
進行調用時,即 x("inner x")
,將會彈出警告訊息,表明 “outer x innerx
”。
清單 4
簡要解釋了 JavaScript
閉包。閉包功能非常強大,原因是它們使內建函式在外部函數返回時也仍然可以保留對此外部函數的變數的訪問。不幸的是,閉包非常易於隱藏
JavaScript 對象 和 DOM 對象間的循環參考。
閉包和循環參考
在清單 5 中,JavaScript 對象(obj
)包含到 DOM 對象的引用(通過 id "element"
被引用)。而 DOM 元素則擁有到 JavaScript obj
的引用。這樣建立起來的 JavaScript 對象和 DOM 對象間的循環參考將會導致記憶體流失。
清單 5. 由事件處理引起的記憶體流失模式
<html><br /><body><br /><mce:script type="text/javascript"><!--<br />document.write("Program to illustrate memory leak via closure");<br />window.onload=function outerFunction(){<br />var obj = document.getElementById("element");<br />obj.onclick=function innerFunction(){<br />alert("Hi! I will leak");<br />};<br />obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));<br />// This is used to make the leak significant<br />};</p><p>// --></mce:script><br /><button id="element">Click Me</button><br /></body><br /></html>
避免記憶體流失
幸好,JavaScript
中的記憶體流失是可以避免的。當確定了可導致循環參考的模式之後,正如我們在上述章節中所做的那樣,您就可以開始著手應對這些模式了。這裡,我們將以上述的
由事件處理引起的記憶體流失模式
為例來展示三種應對已知記憶體流失的方式。
一種應對 清單 5
中的記憶體流失的解決方案是讓此 JavaScript 對象 obj
為空白,這會顯式地打破此循環參考,如清單 6 所示。
清單 6. 打破循環參考
<html><br /><body><br /><mce:script type="text/javascript"><!--<br />document.write("Avoiding memory leak via closure by breaking the circular<br /> reference");<br />window.onload=function outerFunction(){<br />var obj = document.getElementById("element");<br />obj.onclick=function innerFunction()<br />{<br />alert("Hi! I have avoided the leak");<br />// Some logic here<br />};<br />obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));<br />obj = null; //This breaks the circular reference<br />};</p><p>// --></mce:script><br /><button id="element">"Click Here"</button><br /></body><br /></html>
清單 7 是通過添加另一個閉包來避免 JavaScript 對象和 DOM 對象間的循環參考。
清單 7. 添加另一個閉包
<html><br /><body><br /><mce:script type="text/javascript"><!--<br />document.write("Avoiding a memory leak by adding another closure");<br /> window.onload=function outerFunction(){<br />var anotherObj = function innerFunction()<br /> {<br />// Some logic here<br />alert("Hi! I have avoided the leak");<br /> };<br /> (function anotherInnerFunction(){<br />var obj = document.getElementById("element");<br />obj.onclick=anotherObj })();<br /> };</p><p>// --></mce:script><br /><button id="element">"Click Here"</button><br /></body><br /></html>
清單 8 則通過添加另一個函數來避免閉包本身,進而阻止了泄漏。
清單 8. 避免閉包自身
<html><br /><head><br /><mce:script type="text/javascript"><!--<br />document.write("Avoid leaks by avoiding closures!");<br />window.onload=function()<br />{<br />var obj = document.getElementById("element");<br />obj.onclick = doesNotLeak;<br />}<br />function doesNotLeak()<br />{<br />//Your Logic here<br />alert("Hi! I have avoided the leak");<br />}</p><p>// --></mce:script><br /></head><br /><body><br /><button id="element">"Click Here"</button><br /></body><br /></html>
結束語
本文解釋了循環參考是如何導致 JavaScript 中的記憶體流失的 ——
尤其是在結合了閉包的情況下。您還瞭解了涉及到循環參考的一些常見記憶體流失模式以及應對這些泄漏模式的幾種簡單方式
- sIEve-0.0.8.exe.zip
(195.4 KB)
- test_ie.html.zip
(1.4 KB)
- Understanding_and_Solving_Internet_Explorer_Leak_Patterns.rar
(43 KB)
- 理
解並解決IE的記憶體流失方式.rar
(23.1 KB)
- JSLeaksDetector.msi.zip
(216.6 KB)
- winleak_1.1.3.zip
(22.6 KB)
轉自:http://wv1124.javaeye.com/blog/524896