浏覽本站的 Javascript教程 欄目內容。
常規循環引用內存洩漏和Closure內存洩漏
要了解javascript的內存洩漏問題,首先要了解的就是javascript的GC原理。
我記得原來在犀牛書《JavaScript: The Definitive Guide》中看到過,IE使用的GC算法是計數器,因此只碰到循環 引用就會造成memory leakage。後來一直覺得和觀察到的現象很不一致,直到看到Eric的文章,才明白犀牛書的說法沒有說得很明確,估計該書成文後IE升級過算法吧。在IE 6中,對於javascript object內部,jscript使用的是mark-and-sweep算法,而對於javascript object與外部object(包括native object和vbscript object等等)的引用時,IE 6使用的才是計數器的算法。
Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx一文中提到IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。對於javascript對算法的實現缺陷,文章如是說:
"The benefits of this approach are numerous, but the principle benefit is that circular references are not leaked unless the circular reference involves an object not owned by JScript. "
也就是說,IE 6對於純粹的Script Objects間的Circular References是可以正確處理的,可惜它處理不了的是JScript與Native Object(例如Dom、ActiveX Object)之間的Circular References。
所以,當我們出現Native對象(例如Dom、ActiveX Object)與Javascript對象間的循環引用時,內存洩露的問題就出現了。當然,這個bug在IE 7中已經被修復了[http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html]。
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有個示意圖和簡單的例子體現了這個問題:
<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 onload = "SetupLeak()" onunload = "BreakLeak()">
<div id = "LeakedDiv" ></div>
</body>
</html>
上面這個例子,看似很簡單就能夠解決內存洩露的問題。可惜的是,當我們的代碼中的結構復雜了以後,造成循環引用的原因開始變得多樣,我們就沒法那麼容易觀察到了,這時候,我們必須對代碼進行仔細的檢查。
尤其是當碰到Closure,當我們往Native對象(例如Dom對象、ActiveX Object)上綁定事件響應代碼時,一個不小心,我們就會制造出Closure Memory Leak。其關鍵原因,其實和前者是一樣的,也是一個跨javascript object和native object的循環引用。只是代碼更為隱蔽,這個隱蔽性,是由於javascript的語言特性造成的。但在使用類似內嵌函數的時候,內嵌的函數有擁有一個reference指向外部函數的scope,包括外部函數的參數,因此也就很容易造成一個很隱蔽的循環引用,例如:
DOM_Node.onevent ->function_object.[ [ scope ] ] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。
[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp]有個例子極深刻地顯示了該隱蔽性:
<html>
<head>
<script language = "JScript">
function&nbs