常規(guī)循環(huán)引用內(nèi)存泄漏和Closure內(nèi)存泄漏
要了解javascript的內(nèi)存泄漏問(wèn)題,首先要了解的就是javascript的GC原理。
我記得原來(lái)在犀牛書(shū)《JavaScript: The Definitive
Guide》中看到過(guò),IE使用的GC算法是計(jì)數(shù)器,因此只碰到循環(huán) 引用就會(huì)造成memory
leakage。后來(lái)一直覺(jué)得和觀察到的現(xiàn)象很不一致,直到看到Eric的文章,才明白犀牛書(shū)的說(shuō)法沒(méi)有說(shuō)得很明確,估計(jì)該書(shū)成文后IE升級(jí)過(guò)算法吧。
在IE 6中,對(duì)于javascript object內(nèi)部,jscript使用的是mark-and-sweep算法,而對(duì)于javascript
object與外部object(包括native object和vbscript object等等)的引用時(shí),IE 6使用的才是計(jì)數(shù)器的算法。
Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx一文中提到IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。對(duì)于javascript對(duì)算法的實(shí)現(xiàn)缺陷,文章如是說(shuō):
"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. "
也就是說(shuō),IE 6對(duì)于純粹的Script Objects間的Circular
References是可以正確處理的,可惜它處理不了的是JScript與Native Object(例如Dom、ActiveX
Object)之間的Circular References。
所以,當(dāng)我們出現(xiàn)Native對(duì)象(例如Dom、ActiveX Object)與Javascript對(duì)象間的循環(huán)引用時(shí),內(nèi)存泄露的問(wèn)題就出現(xiàn)了。當(dāng)然,這個(gè)bug在IE 7中已經(jīng)被修復(fù)了[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
中有個(gè)示意圖和簡(jiǎn)單的例子體現(xiàn)了這個(gè)問(wèn)題:
???? < head >
???????? < script?language = " JScript " >
???????? var ?myGlobalObject;
???????? function ?SetupLeak()? // 產(chǎn)生循環(huán)引用,因此會(huì)造成內(nèi)存泄露
????????{
???????????? // ?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()? // 解開(kāi)循環(huán)引用,解決內(nèi)存泄露問(wèn)題
????????{
????????????document.getElementById( " LeakedDiv " ).expandoProperty? =
???????????????? null ;
????????}
???????? </ script >
???? </ head >
???? < body?onload = " SetupLeak() " ?onunload = " BreakLeak() " >
???????? < div?id = " LeakedDiv " ></ div >
???? </ body >
</ html >
尤其是當(dāng)碰到Closure,當(dāng)我們往Native對(duì)象(例如Dom對(duì)象、ActiveX
Object)上綁定事件響應(yīng)代碼時(shí),一個(gè)不小心,我們就會(huì)制造出Closure Memory
Leak。其關(guān)鍵原因,其實(shí)和前者是一樣的,也是一個(gè)跨javascript object和native
object的循環(huán)引用。只是代碼更為隱蔽,這個(gè)隱蔽性,是由于javascript的語(yǔ)言特性造成的。但在使用類(lèi)似內(nèi)嵌函數(shù)的時(shí)候,內(nèi)嵌的函數(shù)有擁有一
個(gè)reference指向外部函數(shù)的scope,包括外部函數(shù)的參數(shù),因此也就很容易造成一個(gè)很隱蔽的循環(huán)引用,例如:
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]有個(gè)例子極深刻地顯示了該隱蔽性:
???? < head >
???????? < script?language = " JScript " >
???????? function ?AttachEvents(element)
????????{
???????????? // ?This?structure?causes?element?to?ref?ClickEventHandler??//element有個(gè)引用指向函數(shù)ClickEventHandler()
????????????element.attachEvent( " onclick " ,?ClickEventHandler);
???????????? function ?ClickEventHandler()
????????????{
???????????????? // ?This?closure?refs?element??//該函數(shù)有個(gè)引用指向AttachEvents(element)調(diào)用Scope,也就是執(zhí)行了參數(shù)element。
????????????????
????????????}
????????}
???????? function ?SetupLeak()
????????{
???????????? // ?The?leak?happens?all?at?once
????????????AttachEvents(document.getElementById( " LeakedDiv " ));
????????}
???????? </ script >
???? </ head >
???? < body?onload = " SetupLeak() " ?onunload = " BreakLeak() " >
???????? < div?id = " LeakedDiv " ></ div >
???? </ body >
</ html >
還有這個(gè)例子在IE 6中同樣原因會(huì)引起泄露
function ?leakmaybe()?{
var ?elm? = ?document.createElement( " DIV " );
??elm.onclick? = ? function ()?{
return ? 2 ? + ? 2 ;
??}
}
for ?( var ?i? = ? 0 ;?i?? 10000 ;?i ++ )?{
??leakmaybe();
}
btw:
關(guān)于Closure的知識(shí),大家可以看看http://jibbering.com/faq/faq_notes/closures.html這篇文章,習(xí)慣中文也可以看看zkjbeyond的blog,他對(duì)Closure這篇文章進(jìn)行了簡(jiǎn)要的翻譯:http://www.aygfsteel.com/zkjbeyond/archive/2006/05/19/47025.html
。
之所以會(huì)有這一系列的問(wèn)題,關(guān)鍵就在于javascript是種函數(shù)式腳本解析語(yǔ)言,因此javascript中“函數(shù)中的變量的作用域是定義作用域,而
不是動(dòng)態(tài)作用域”,這點(diǎn)在犀牛書(shū)《JavaScript: The Definitive Guide》中的“Funtion”一章中有所討論。
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555中也對(duì)這個(gè)問(wèn)題舉了很詳細(xì)的例子。
一些 簡(jiǎn)單的解決方案
目前大多數(shù)ajax前端的javascript framework都利用對(duì)事件的管理,解決了該問(wèn)題。
如果你需要自己解決這個(gè)問(wèn)題,可以參考以下的一些方法:
-
http://outofhanwell.com/ieleak/index.php?title=Main_Page:有個(gè)不錯(cuò)的檢測(cè)工具
-
http://youngpup.net/2005/0221010713
中提到:可以利用遞歸Dom樹(shù),解除event綁定,從而解除循環(huán)引用:
if (window.attachEvent) {
var clearElementProps = [
'data',
'onmouseover',
'onmouseout',
'onmousedown',
'onmouseup',
'ondblclick',
'onclick',
'onselectstart',
'oncontextmenu'
];
window.attachEvent("onunload", function() {
var el;
for(var d = document.all.length;d--;){
el = document.all[d];
for(var c = clearElementProps.length;c--;){
el[clearElementProps[c]] = null;
}
}
});
}
-
而http://novemberborn.net/javascript/event-cache
一文中則通過(guò)增加EventCache,從而給出一個(gè)相對(duì)結(jié)構(gòu)化的解決方案
/* ????EventCache?Version?1.0
????Copyright?2005?Mark?Wubben
????Provides?a?way?for?automagically?removing?events?from?nodes?and?thus?preventing?memory?leakage.
????See?<http://novemberborn.net/javascript/event-cache>?for?more?information.
????
????This?software?is?licensed?under?the?CC-GNU?LGPL?<http://creativecommons.org/licenses/LGPL/2.1/>
*/
/* ????Implement?array.push?for?browsers?which?don't?support?it?natively.
????Please?remove?this?if?it's?already?in?other?code? */
if (Array.prototype.push? == ? null ){
????Array.prototype.push? = ? function (){
???????? for ( var ?i? = ? 0 ;?i? < ?arguments.length;?i ++ ){
???????????? this [ this .length]? = ?arguments[i];
????????};
???????? return ? this .length;
????};
};
/* ????Event?Cache?uses?an?anonymous?function?to?create?a?hidden?scope?chain.
????This?is?to?prevent?scoping?issues.? */
var ?EventCache? = ? function (){
???? var ?listEvents? = ?[];
????
???? return ?{
????????listEvents?:?listEvents,
????
????????add?:? function (node,?sEventName,?fHandler,?bCapture){
????????????listEvents.push(arguments);
????????},
????
????????flush?:? function (){
???????????? var ?i,?item;
???????????? for (i? = ?listEvents.length? - ? 1 ;?i? >= ? 0 ;?i? = ?i? - ? 1 ){
????????????????item? = ?listEvents[i];
????????????????
???????????????? if (item[ 0 ].removeEventListener){
????????????????????item[ 0 ].removeEventListener(item[ 1 ],?item[ 2 ],?item[ 3 ]);
????????????????};
????????????????
???????????????? /* ?From?this?point?on?we?need?the?event?names?to?be?prefixed?with?'on"? */
???????????????? if (item[ 1 ].substring( 0 ,? 2 )? != ? " on " ){
????????????????????item[ 1 ]? = ? " on " ? + ?item[ 1 ];
????????????????};
????????????????
???????????????? if (item[ 0 ].detachEvent){
????????????????????item[ 0 ].detachEvent(item[ 1 ],?item[ 2 ]);
????????????????};
????????????????
????????????????item[ 0 ][item[ 1 ]]? = ? null ;
????????????};
????????}
????};
}();
-
使用方法也很簡(jiǎn)單:
<script type="text/javascript">function addEvent(oEventTarget, sEventType, fDest){
if(oEventTarget.attachEvent){
oEventTarget.attachEvent("on" + sEventType, fDest);
} elseif(oEventTarget.addEventListener){
oEventTarget.addEventListener(sEventType, fDest, true);
} elseif(typeof oEventTarget[sEventType] == "function"){
var fOld = oEventTarget[sEventType];
oEventTarget[sEventType] = function(e){ fOld(e); fDest(e); };
} else {
oEventTarget[sEventType] = fDest;
};
/* Implementing EventCache for all event systems */
EventCache.add(oEventTarget, sEventType, fDest, true);
};
function createLeak(){
var body = document.body;
function someHandler(){return body;
};
addEvent(body, "click", someHandler);
};
window.onload = function(){
var i = 500;
while(i > 0){
createLeak();
i = i - 1;
}
};
window.onunload = EventCache.flush;
</script>
-
http://talideon.com/weblog/2005/03/js-memory-leaks.cfm
一文中的方法類(lèi)似:
/*
?*?EventManager.js
?*?by?Keith?Gaughan
?*
?*?This?allows?event?handlers?to?be?registered?unobtrusively,?and?cleans
?*?them?up?on?unload?to?prevent?memory?leaks.
?*
?*?Copyright?(c)?Keith?Gaughan,?2005.
?*
?*?All?rights?reserved.?This?program?and?the?accompanying?materials
?*?are?made?available?under?the?terms?of?the?Common?Public?License?v1.0
?*?(CPL)?which?accompanies?this?distribution,?and?is?available?at
?*?http://www.opensource.org/licenses/cpl.php
?*
?*?This?software?is?covered?by?a?modified?version?of?the?Common?Public?License
?*?(CPL),?where?Keith?Gaughan?is?the?Agreement?Steward,?and?the?licensing
?*?agreement?is?covered?by?the?laws?of?the?Republic?of?Ireland.
? */
// ?For?implementations?that?don't?include?the?push()?methods?for?arrays.
if ?( ! Array.prototype.push)?{
????Array.prototype.push? = ? function (elem)?{
???????? this [ this .length]? = ?elem;
????}
}
var ?EventManager? = ?{
????_registry:? null ,
????Initialise:? function ()?{
???????? if ?( this ._registry? == ? null )?{
???????????? this ._registry? = ?[];
???????????? // ?Register?the?cleanup?handler?on?page?unload.
????????????EventManager.Add(window,? " unload " ,? this .CleanUp);
????????}
????},
???? /* *
?????*?Registers?an?event?and?handler?with?the?manager.
?????*
?????*?@param??obj?????????Object?handler?will?be?attached?to.
?????*?@param??type????????Name?of?event?handler?responds?to.
?????*?@param??fn??????????Handler?function.
?????*?@param??useCapture??Use?event?capture.?False?by?default.
?????*?????????????????????If?you?don't?understand?this,?ignore?it.
?????*
?????*?@return?True?if?handler?registered,?else?false.
????? */
????Add:? function (obj,?type,?fn,?useCapture)?{
???????? this .Initialise();
???????? // ?If?a?string?was?passed?in,?it's?an?id.
???????? if ?( typeof ?obj? == ? " string " )?{
????????????obj? = ?document.getElementById(obj);
????????}
???????? if ?(obj? == ? null ? || ?fn? == ? null )?{
???????????? return ? false ;
????????}
???????? // ?Mozilla/W3C?listeners?
???????? if ?(obj.addEventListener)?{
????????????obj.addEventListener(type,?fn,?useCapture);
???????????? this ._registry.push({obj:?obj,?type:?type,?fn:?fn,?useCapture:?useCapture});
???????????? return ? true ;
????????}
???????? // ?IE-style?listeners?
???????? if ?(obj.attachEvent? && ?obj.attachEvent( " on " ? + ?type,?fn))?{
???????????? this ._registry.push({obj:?obj,?type:?type,?fn:?fn,?useCapture:? false });
???????????? return ? true ;
????????}
???????? return ? false ;
????},
???? /* *
?????*?Cleans?up?all?the?registered?event?handlers.
????? */
????CleanUp:? function ()?{
???????? for ?( var ?i? = ? 0 ;?i? < ?EventManager._registry.length;?i ++ )?{
???????????? with ?(EventManager._registry[i])?{
???????????????? // ?Mozilla/W3C?listeners?
???????????????? if ?(obj.removeEventListener)?{
????????????????????obj.removeEventListener(type,?fn,?useCapture);
????????????????}
???????????????? // ?IE-style?listeners?
???????????????? else ? if ?(obj.detachEvent)?{
????????????????????obj.detachEvent( " on " ? + ?type,?fn);
????????????????}
????????????}
????????}
???????? // ?Kill?off?the?registry?itself?to?get?rid?of?the?last?remaining
???????? // ?references.
????????EventManager._registry? = ? null ;
????}
};
使用起來(lái)也很簡(jiǎn)單
<html><head>
<script type=text/javascript src=EventManager.js></script>
<script type=text/javascript>
function onLoad() {
EventManager.Add(document.getElementById(testCase),click,hit );
returntrue;
}
function hit(evt) {
alert(click);
}
</script>
</head>
<body onload='javascript: onLoad();'>
<div id='testCase' style='width:100%; height: 100%; background-color: yellow;'>
<h1>Click me!</h1>
</div>
</body>
</html>
- google map api同樣提供了一個(gè)類(lèi)似的函數(shù)用在頁(yè)面的unload事件中,解決Closure帶來(lái)的內(nèi)存泄露問(wèn)題。
-
當(dāng)然,如果你不嫌麻煩,你也可以為每個(gè)和native object有關(guān)的就阿vascript object編寫(xiě)一個(gè)destoryMemory函數(shù),用來(lái)手動(dòng)調(diào)用,從而手動(dòng)解除Dom對(duì)象的事件綁定。
-
還有一種就是不要那么OO,拋棄Dom的一些特性,用innerHTML代替appendChild,避開(kāi)循環(huán)引用。詳細(xì)見(jiàn)http://birdshome.cnblogs.com/archive/2005/02/16/104967.html
中的討論貼。
Cross-Page Leaks
??? Cross-Page Leaks和下一節(jié)提到的Pseudo-Leaks在我看來(lái),就是IE的bug, 雖然MS死皮賴(lài)臉不承認(rèn):)
???? 大家可以看看這段例子代碼:
???? < head >
???????? < script?language = " JScript " >
???????? function ?LeakMemory()? // 這個(gè)函數(shù)會(huì)引發(fā)Cross-Page?Leaks
????????{
???????????? 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()? // 而這個(gè)函數(shù)不會(huì)引發(fā)Cross-Page?Leaks
????????{
???????????? 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?onclick = " LeakMemory() " > Memory?Leaking?Insert </ button >
???????? < button?onclick = " CleanMemory() " > Clean?Insert </ button >
???????? < div?id = " hostElement " ></ div >
???? </ body >
</ html >
LeakMemory和CleanMemory這兩段函數(shù)的唯一區(qū)別就在于他們的代碼的循序,從代碼上看,兩段代碼的邏輯都沒(méi)有錯(cuò)。
但LeakMemory卻會(huì)造成泄露。原因是LeakMemory()會(huì)先建立起parentDiv和childDiv之間的連接,這時(shí)候,為了讓
childDiv能夠獲知parentDiv的信息,因此IE需要先建立一個(gè)臨時(shí)的scope對(duì)象。而后parentDiv建立了和
hostElement對(duì)象的聯(lián)系,parentDiv和childDiv直接使用頁(yè)面document的scope。可惜的是,IE不會(huì)釋放剛才那個(gè)臨
時(shí)的scope對(duì)象的內(nèi)存空間,直到我們跳轉(zhuǎn)頁(yè)面,這塊空間才能被釋放。而CleanMemory函數(shù)不同,他先把parentDiv和
hostElement建立聯(lián)系,而后再把childDiv和parentDiv建立聯(lián)系,這個(gè)過(guò)程不需要單獨(dú)建立臨時(shí)的scope,只要直接使用頁(yè)面
document的scope就可以了, 所以也就不會(huì)造成內(nèi)存泄露了
詳細(xì)原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp這篇文章。
btw:
IE 6中垃圾回收算法,就是從那些直接"in scope"的對(duì)象開(kāi)始進(jìn)行mark清除的:
Every variable which is "in scope" is called a "scavenger". A scavenger
may refer to a number, an object, a string, whatever. We maintain a
list of scavengers – variables are moved on to the scav list when they
come into scope and off the scav list when they go out of scope.
Pseudo-Leaks
這個(gè)被稱(chēng)為“秀逗泄露”真是恰當(dāng)啊:)
看看這個(gè)例子:
???? < 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()?{?} " ;//看內(nèi)存會(huì)不斷增加
????????????}
????????}
???????? </ script >
???? </ head >
???? < body >
???????? < button?onclick = " LeakMemory() " > Memory?Leaking?Insert </ button >
???????? < script?id = " hostElement " > function ?foo()?{?} </ script >
???? </ body >
</ html >
MS是這么解釋的,這不是內(nèi)存泄漏。如果您創(chuàng)建了許多無(wú)法獲得也無(wú)法釋放的對(duì)象,那才是內(nèi)存泄漏。在這里,您將創(chuàng)建許多元素,Internet Explorer 需要保存它們以正確呈現(xiàn)頁(yè)面。Internet Explorer 并不知道您以后不會(huì)運(yùn)行操縱您剛剛創(chuàng)建的所有這些對(duì)象的腳本。當(dāng)頁(yè)面消失時(shí)(當(dāng)您瀏覽完,離開(kāi)瀏覽器時(shí))會(huì)釋放內(nèi)存。它不會(huì)泄漏。當(dāng)銷(xiāo)毀頁(yè)面時(shí),會(huì)中斷循 環(huán)引用。
唉~~~
詳細(xì)原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp這篇文章。
其它一些瑣碎的注意點(diǎn)
變量定義一定要用var,否則隱式聲明出來(lái)的變量都是全局變量,不是局部變量;
全局變量沒(méi)用時(shí)記得要置null;
注意正確使用delete,刪除沒(méi)用的一些函數(shù)屬性;
注意正確使用try...cache,確保去處無(wú)效引用的代碼能被正確執(zhí)行;
open出來(lái)的窗口即使close了,它的window對(duì)象還是存在的,要記得刪除引用;
frame和iframe的情況和窗口的情況類(lèi)似。
參考資料
http://jibbering.com/faq/faq_notes/closures.html
http://javascript.weblogsinc.com/2005/03/07/javascript-memory-leaks/
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp
http://72.14.203.104/search?q=cache:V9Bt4_HBzQ8J:jgwebber.blogspot.com/2005/01/dhtml-leaks-like-sieve.html+DHTML+Leaks+Like+a+Sieve+&hl=zh-CN&ct=clnk&cd=9
(這是DHTML Leaks Like a Sieve)一文在google上的cache,原文已經(jīng)連不上了)
http://spaces.msn.com/siteexperts/Blog/cns!1pNcL8JwTfkkjv4gg6LkVCpw!338.entry
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555
http://www.ajaxtopics.com/leakpatterns.html
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53028.aspx
http://www.quirksmode.org/blog/archives/2005/02/javascript_memo.html
http://youngpup.net/2005/0221010713
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx =
http://support.microsoft.com/kb/266071/EN-US ==>IE 5.0至5.5一些版本中的GC bug
http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html ==>ie 7的改進(jìn)
http://erik.eae.net/archives/2006/04/26/23.23.02/ ==>ie 7的改進(jìn)
http://www.feedbackarchive.com/spamvampire/today.html ==> Try this script for memory leaks - it leaked 50 megabytes in 15 minutes with firefox on linux:
http://birdshome.cnblogs.com/archive/2005/02/15/104599.html
http://www.quirksmode.org/dom/innerhtml.html
http://www.crockford.com/javascript/memory/leak.html
《JavaScript: The Definitive Guide》4th Edition
http://outofhanwell.com/ieleak/index.php?title=Main_Page
不過(guò)這里的“內(nèi)存溢出”似乎應(yīng)該是“內(nèi)存泄漏”。
SetupLeak() 函數(shù)中
myGlobalObject = document.getElementById( " LeakedDiv " );
則 myGlobalObject有了一個(gè)指向DOM對(duì)象 " LeakedDiv " 的引用,
而后
document.getElementById( " LeakedDiv " ).expandoProperty = myGlobalObject;
使得DOM對(duì)象 " LeakedDiv " 有一個(gè)屬性expandoProperty引用指向myGlobalObject;
這樣myGlobalObject和DOM對(duì)象 " LeakedDiv "分別有引用指向?qū)Ψ剑跃脱h(huán)引用了。
函數(shù)BreakLeak() 則清除了DOM對(duì)象" LeakedDiv " 向myGlobalObject的引用,
解決循環(huán)引用問(wèn)題。
============================
如果你問(wèn)的是第二段代碼,那么就比較復(fù)雜了。
這涉及到Closure的問(wèn)題,你可以看看文章中給出的其他鏈接文章,例如zkjbeyond對(duì)Closure一文的翻譯。
大致的原因是因?yàn)镃lickEventHandler()是AttachEvents()的一個(gè)內(nèi)嵌函數(shù),
AttachEvents()函數(shù)執(zhí)行后,把ClickEventHandler()函數(shù)綁定在element對(duì)象的"onclick"事件上。這樣,element對(duì)象就有個(gè)間接引用指向ClickEventHandler()函數(shù)的一個(gè)實(shí)例。
又正是因?yàn)镃lickEventHandler()是一個(gè)內(nèi)嵌函數(shù),因此會(huì)為這個(gè)實(shí)例構(gòu)造一個(gè)scope,這個(gè)scope中包括了父函數(shù),也就是AttachEvents()函數(shù)中的局部變量和參數(shù),而這兒的參數(shù)剛好就是element,所以ClickEventHandler()函數(shù)的實(shí)例同時(shí)又間接引用了element。
說(shuō)得有些繞.......呵呵
多謝:) 已修正。
不過(guò)向毛主席保證,“溢出”一定不是偶翻譯的,哈哈~~
http://www.tgod.com.cn
提供代理功能,推薦大家看看
我見(jiàn)過(guò)最好的代理網(wǎng)站
[引用提示]Liu Jian引用了該文章, 地址: http://www.cnblogs.com/LiuJian2005/archive/2008/05/30/1210437.html
document.getElementById( " LeakedDiv " ).expandoProperty =
document.getElementById( " LeakedDiv " )吧.
SetupLeak執(zhí)行后效果難道不是
domobj.expandoProperty = domobj
myJSObj = domobj;
???
我感覺(jué)這根本沒(méi)有出現(xiàn)js對(duì)象和native對(duì)象的循環(huán)引用啊,應(yīng)該只是native對(duì)象自身的循環(huán)啊.
至于native對(duì)象自身循環(huán)引用會(huì)不會(huì)導(dǎo)致內(nèi)存泄漏我就不知道了....
如果改成
var myGlobalObject = {};
function SetupLeak() // 產(chǎn)生循環(huán)引用,因此會(huì)造成內(nèi)存泄露
{
// First set up the script scope to element reference
myGlobalObject.domobj = document.getElementById("LeakedDiv");
// Next set up the element to script scope reference
document.getElementById( " LeakedDiv " ).expandoProperty =
myGlobalObject;
}
那就產(chǎn)生內(nèi)存泄漏了.
因?yàn)樵谠瓉?lái)的那個(gè)代碼中,myGlobalObject只是一個(gè)變量,指向domobj,而自身并不是一個(gè)jsobject.
這段代碼應(yīng)該是在微軟官方的文章上改的,不過(guò)好像改失敗了,囧...
原文這樣的:
<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 onload="SetupLeak()" onunload="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>
個(gè)人看法,大家繼續(xù)討論...被內(nèi)存泄漏折騰壞了,好像firefox也有.
你想多了:),變量是什么?
你所說(shuō)“myGlobalObject只是一個(gè)變量,指向domobj,而自身并不是一個(gè)jsobject.” 沒(méi)有錯(cuò),但是不是“jsobject”不是重點(diǎn)。
重點(diǎn)在于“IE 6處理不了的是JScript與Native Object(例如Dom、ActiveX Object)之間的Circular References”。
關(guān)鍵字是“References引用”:)
你只要考慮一下GC是憑什么來(lái)決定是否回收的就可以明白。