1. ie 版本 <=6 循环引用出现:
<html>
<head>
<script type="text/javascript">
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;
// 或者通过事件属性注册形的闭包而造成的循环引用
document.getElementById("LeakedDiv").onclick = function() {
};
}
// 解开循环引用,解决内存泄露问题
function BreakLeak() {
return;
document.getElementById("LeakedDiv").expandoProperty = null;
document.getElementById("LeakedDiv").innerHTML = '';
return confirm("ok?");
}
</script>
</head>
<body οnlοad="SetupLeak()" οnunlοad="BreakLeak()">
<div id="LeakedDiv">xxxxxxxxx</div>
</body>
</html>
上述代码展示了两种因为循环引用导致内存泄露的例子。
1. dom 节点的属性引用到自身节点,甚至包括不直接的引用,例如:
(function(){ var d={b:document.body} var obj={doc:d}; // ← obj.doc.b === document.body document.body.o=obj; // ← Circular loop: document.body.o.doc.b === document.body })();
2. dom 节点的事件 handler 属性设置监听器函数,监听器函数的 [[scope]] 作用域会引用到该 dom 节点而造成隐蔽的循环引用。
(function(){ var b=document.body; // ← create a reference to document.body inside of the outer scope. b.οnclick=function() { // ← b.onclick refers to a function. // this function can access "b" due to closure // do something... }; })();
注意事件使用 ie DOM2 的模式(attachEvent)注册事件时,由于没有直接和DOM属性接触,不会产生dom节点引用监听器函数,因而也不会产生循环引用,也不会带来内存泄露。
PS:类似 DOM,COM 在 ie 中也会因为循环引用而造成内存问题,譬如常见用于ajax 的 activeX:
(function(){ var x=getXHRobject(); x.onreadystatechange=function() { // ← create link from x (COM object) to anonymous function if(x.readystate==4){ // ← reference to x exists inside function scope, creating circular link. // do something. //消除循环引用 x.onreadystatechange=null; } }; })();
解决:
ajax 的问题解决就比较简单了,只要在 onreadystatechange 调用函数中将这个属性清空就行了。
而对于 dom ,则建议是尽可能的从 javascript object 引用 dom object,而不要从 dom object 引用到 javascript object,如果一定要这样做的话,下面给出了一段解决代码,在页面 unload 时清楚已设置的属性。
(function(){ var unLoaders=[]; myDomNode.object=new myObject(); // ← let’s say that this creates a leak somewhere unLoaders.push(myDomNode); // ← save it for later // create an “unload” function var unload=function(){ for(var i=unLoaders.length-1;i>-1;i–){ unLoaders[i].object=null; // ← break the cycle } }; // run the unload function on window.unload YAHOO.util.Event.addListener(window,’unload’,unload); })();
关键是:这个 bug 出现时即使刷新页面,内存仍然不会被释放 。
这个bug 已经在 ie >=7 以及 xp sp3上修复。详见 Memory Leaks in Microsoft Internet Explorer 。
2. ie removeChild 时出现孤立节点问题
ie 下面清除一个节点,调用 dom removeChild 的方法会出现该节点的内存并没有真正释放,ie内存也没有减少,(但是刷新页面会释放),所以对于ie要调用 parent.innerHTML = ""; 的方法来强制释放内存。(另外在之前还要清除该元素的所有监听事件,否则dom有引用仍然是释放不了,ie 中 Jscript 和 Dom 是分开的)。
详见:extjs-3.0 Element.js remove 方法
/** * Removes a DOM node from the document. The body node will be ignored if passed in. * @param {HTMLElement} node The node to remove */ removeNode : isIE ? function(){ var d; return function(n){ if(n && n.tagName != 'BODY'){ d = d || DOC.createElement('div'); d.appendChild(n); d.innerHTML = ''; } } }() : function(n){ if(n && n.parentNode && n.tagName != 'BODY'){ n.parentNode.removeChild(n); } }
总结:
一句话:尽量少使用 dom 自定义属性,尽量使用成熟类库注册事件。
参考资料:
对 ie 的内存缺陷算法感兴趣的话可以在以下文章中找到。
http://fins.iteye.com/blog/172891
http://oznyang.iteye.com/blog/180611?page=2
http://www.blogjava.net/tim-wu/archive/2006/05/29/48729.html
http://birdshome.cnblogs.com/archive/2005/02/16/104967.html