导读:
要了解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在一文中提到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中已经被修复了[]。中有个示意图和简单的例子体现了这个问题:
????????<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>???? ???????? ????
上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。
尤其是当碰到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。
????????<script?language="JScript">????????function?AttachEvents(element)
{
//?This?structure?causes?element?to?ref?ClickEventHandler??//element有个引用指向函数ClickEventHandler()
element.attachEvent("onclick",?ClickEventHandler);
function?ClickEventHandler()
{
//?This?closure?refs?element??//该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
}
}
function?SetupLeak()
{
//?The?leak?happens?all?at?once
AttachEvents(document.getElementById("LeakedDiv"));
}
</script>???? ???????? ????
还有这个例子在IE 6中同样原因会引起泄露
function?leakmaybe()?{var?elm?=?document.createElement("DIV");
elm.onclick?=?function()?{return?2?+?2??}
}for?(var?i?=?0?i??10000?i++)?{
leakmaybe();
}
btw:
关于Closure的知识,大家可以看看这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:。之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。中也对这个问题举了很详细的例子。
一些 简单的解决方案
目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。
如果你需要自己解决这个问题,可以参考以下的一些方法:
http://outofhanwell.com/ieleak/index.php?title=Main_Page:有个不错的检测工具中提到:可以利用递归Dom树,解除event绑定,从而解除循环引用:
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;
}
}
});
}
而一文中则通过增加EventCache,从而给出一个相对结构化的解决方案
/*????EventCache?Version?1.0
Copyright?2005?Mark?Wubben
Provides?a?way?for?automagically?removing?events?from?nodes?and?thus?preventing?memory?leakage.
See? ?for?more?information.
This?software?is?licensed?under?the?CC-GNU?LGPL? */
/*????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? 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????????????};
}
};
}();
使用方法也很简单:
<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"){
varfOld = oEventTarget[sEventType];
oEventTarget[sEventType] = function(e){ fOld(e); fDest(e); };
} else{
oEventTarget[sEventType] = fDest;
};
/* Implementing EventCache forall event systems */
EventCache.add(oEventTarget, sEventType, fDest, true);
};
function createLeak(){
varbody = document.body;
function someHandler(){
returnbody;
};
addEvent(body, "click", someHandler);
};
window.onload = function(){
vari = 500;
while(i >0){
createLeak();
i = i - 1;
}
};
window.onunload = EventCache.flush;
</script>
/*?*?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? 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????}
};
使用起来也很简单
>
<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>
google map api同样提供了一个类似的函数用在页面的unload事件中,解决Closure带来的内存泄露问题。
当然,如果你不嫌麻烦,你也可以为每个和native object有关的就阿vascript object编写一个destoryMemory函数,用来手动调用,从而手动解除Dom对象的事件绑定。
还有一种就是不要那么OO,抛弃Dom的一些特性,用innerHTML代替appendChild,避开循环引用。详细见中的讨论贴。
Cross-Page Leaks
Cross-Page Leaks和下一节提到的Pseudo-Leaks在我看来,就是IE的bug, 虽然MS死皮赖脸不承认:)
大家可以看看这段例子代码:
????????<script?language="JScript">????????function?LeakMemory()?//这个函数会引发Cross-Page?Leaks
{
var?hostElement?=?document.getElementById("hostElement");
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
var?parentDiv?=????????????????????document.createElement(" ");>
var?childDiv?=????????????????????document.createElement(" ");
//?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()?//而这个函数不会引发Cross-Page?Leaks
{
var?hostElement?=?document.getElementById("hostElement");
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
var?parentDiv?=? document.createElement(" ");>
var?childDiv?=? document.createElement(" ");
//?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>???????????? Memory?Leaking?Insert???????? Clean?Insert???????? ????
LeakMemory和CleanMemory这两段函数的唯一区别就在于他们的代码的循序,从代码上看,两段代码的逻辑都没有错。
但LeakMemory却会造成泄露。原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让 childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。而后parentDiv建立了和 hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临时的scope对象的内存空间,直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和 hostElement建立联系,而后再把childDiv和parentDiv建立联系,这个过程不需要单独建立临时的scope,只要直接使用页面 document的scope就可以了, 所以也就不会造成内存泄露了
btw:
IE 6中垃圾回收算法,就是从那些直接"in scope"的对象开始进行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
这个被称为“秀逗泄露”真是恰当啊:)
看看这个例子:
???????? ????????function?LeakMemory()
{
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
hostElement.text?=?"function?foo()?{?}"//看内存会不断增加
}
}
");>
MS是这么解释的,这不是内存泄漏。如果您创建了许多无法获得也无法释放的对象,那才是内存泄漏。在这里,您将创建许多元素,Internet Explorer 需要保存它们以正确呈现页面。Internet Explorer并不知道您以后不会运行操纵您刚刚创建的所有这些对象的脚本。当页面消失时(当您浏览完,离开浏览器时)会释放内存。它不会泄漏。当销毁页面时,会中断循环引用。
唉~~~
其它一些琐碎的注意点
变量定义一定要用var,否则隐式声明出来的变量都是全局变量,不是局部变量;
全局变量没用时记得要置null;
注意正确使用delete,删除没用的一些函数属性;
注意正确使用try...cache,确保去处无效引用的代码能被正确执行;
open出来的窗口即使close了,它的window对象还是存在的,要记得删除引用;
frame和iframe的情况和窗口的情况类似。
参考资料(这是DHTML Leaks Like a Sieve)一文在google上的cache,原文已经连不上了)
本文转自
http://www.blogjava.net/tim-wu/archive/2006/05/29/48729.html
");>>
要了解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在一文中提到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中已经被修复了[]。中有个示意图和简单的例子体现了这个问题:
????????<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>???? ???????? ????
上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。
尤其是当碰到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。
????????<script?language="JScript">????????function?AttachEvents(element)
{
//?This?structure?causes?element?to?ref?ClickEventHandler??//element有个引用指向函数ClickEventHandler()
element.attachEvent("onclick",?ClickEventHandler);
function?ClickEventHandler()
{
//?This?closure?refs?element??//该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
}
}
function?SetupLeak()
{
//?The?leak?happens?all?at?once
AttachEvents(document.getElementById("LeakedDiv"));
}
</script>???? ???????? ????
还有这个例子在IE 6中同样原因会引起泄露
function?leakmaybe()?{var?elm?=?document.createElement("DIV");
elm.onclick?=?function()?{return?2?+?2??}
}for?(var?i?=?0?i??10000?i++)?{
leakmaybe();
}
btw:
关于Closure的知识,大家可以看看这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:。之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。中也对这个问题举了很详细的例子。
一些 简单的解决方案
目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。
如果你需要自己解决这个问题,可以参考以下的一些方法:
http://outofhanwell.com/ieleak/index.php?title=Main_Page:有个不错的检测工具中提到:可以利用递归Dom树,解除event绑定,从而解除循环引用:
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;
}
}
});
}
而一文中则通过增加EventCache,从而给出一个相对结构化的解决方案
/*????EventCache?Version?1.0
Copyright?2005?Mark?Wubben
Provides?a?way?for?automagically?removing?events?from?nodes?and?thus?preventing?memory?leakage.
See? ?for?more?information.
This?software?is?licensed?under?the?CC-GNU?LGPL? */
/*????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? 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????????????};
}
};
}();
使用方法也很简单:
<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"){
varfOld = oEventTarget[sEventType];
oEventTarget[sEventType] = function(e){ fOld(e); fDest(e); };
} else{
oEventTarget[sEventType] = fDest;
};
/* Implementing EventCache forall event systems */
EventCache.add(oEventTarget, sEventType, fDest, true);
};
function createLeak(){
varbody = document.body;
function someHandler(){
returnbody;
};
addEvent(body, "click", someHandler);
};
window.onload = function(){
vari = 500;
while(i >0){
createLeak();
i = i - 1;
}
};
window.onunload = EventCache.flush;
</script>
/*?*?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? 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????}
};
使用起来也很简单
>
<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>
Click me!
google map api同样提供了一个类似的函数用在页面的unload事件中,解决Closure带来的内存泄露问题。
当然,如果你不嫌麻烦,你也可以为每个和native object有关的就阿vascript object编写一个destoryMemory函数,用来手动调用,从而手动解除Dom对象的事件绑定。
还有一种就是不要那么OO,抛弃Dom的一些特性,用innerHTML代替appendChild,避开循环引用。详细见中的讨论贴。
Cross-Page Leaks
Cross-Page Leaks和下一节提到的Pseudo-Leaks在我看来,就是IE的bug, 虽然MS死皮赖脸不承认:)
大家可以看看这段例子代码:
????????<script?language="JScript">????????function?LeakMemory()?//这个函数会引发Cross-Page?Leaks
{
var?hostElement?=?document.getElementById("hostElement");
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
var?parentDiv?=????????????????????document.createElement(" ");>
var?childDiv?=????????????????????document.createElement(" ");
//?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()?//而这个函数不会引发Cross-Page?Leaks
{
var?hostElement?=?document.getElementById("hostElement");
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
var?parentDiv?=? document.createElement(" ");>
var?childDiv?=? document.createElement(" ");
//?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>???????????? Memory?Leaking?Insert???????? Clean?Insert???????? ????
LeakMemory和CleanMemory这两段函数的唯一区别就在于他们的代码的循序,从代码上看,两段代码的逻辑都没有错。
但LeakMemory却会造成泄露。原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让 childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。而后parentDiv建立了和 hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临时的scope对象的内存空间,直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和 hostElement建立联系,而后再把childDiv和parentDiv建立联系,这个过程不需要单独建立临时的scope,只要直接使用页面 document的scope就可以了, 所以也就不会造成内存泄露了
btw:
IE 6中垃圾回收算法,就是从那些直接"in scope"的对象开始进行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
这个被称为“秀逗泄露”真是恰当啊:)
看看这个例子:
???????? ????????function?LeakMemory()
{
//?Do?it?a?lot,?look?at?Task?Manager?for?memory?response
for(i?=?0?i? {
hostElement.text?=?"function?foo()?{?}"//看内存会不断增加
}
}
");>
MS是这么解释的,这不是内存泄漏。如果您创建了许多无法获得也无法释放的对象,那才是内存泄漏。在这里,您将创建许多元素,Internet Explorer 需要保存它们以正确呈现页面。Internet Explorer并不知道您以后不会运行操纵您刚刚创建的所有这些对象的脚本。当页面消失时(当您浏览完,离开浏览器时)会释放内存。它不会泄漏。当销毁页面时,会中断循环引用。
唉~~~
其它一些琐碎的注意点
变量定义一定要用var,否则隐式声明出来的变量都是全局变量,不是局部变量;
全局变量没用时记得要置null;
注意正确使用delete,删除没用的一些函数属性;
注意正确使用try...cache,确保去处无效引用的代码能被正确执行;
open出来的窗口即使close了,它的window对象还是存在的,要记得删除引用;
frame和iframe的情况和窗口的情况类似。
参考资料(这是DHTML Leaks Like a Sieve)一文在google上的cache,原文已经连不上了)
本文转自
http://www.blogjava.net/tim-wu/archive/2006/05/29/48729.html
");>>