参考文章: Winter 的《浏览器中的内存泄露》
还有两篇文章:
IE's memory-leak fix greatly exaggerated
Memory Leakage in Internet Explorer – revisited
IE中内存泄露的几种方式:
1、循环引用(Circular References) — IE浏览器的COM组件产生的对象实例和网页脚本引擎产生的对象实例相互引用,就会造成内存泄漏。这也是Web页面中我们遇到的最常见和主要的泄漏方式;
2、内部函数引用(Closures) — Closures可以看成是目前引起大量问题的循环应用的一种特殊形式。由于依赖指定的关键字和语法结构,Closures调用是比较容易被我们发现的;
3、页面交叉泄漏(Cross-Page Leaks) — 页面交叉泄漏其实是一种较小的泄漏,它通常在你浏览过程中,由于内部对象薄计引起。下面我们会讨论DOM插入顺序的问题,在那个示例中你会发现只需要改动少量的代码,我们就可以避免对象薄计对对象构建带来的影响;
4、貌似泄漏(Pseudo-Leaks) — 这个不是真正的意义上的泄漏,不过如果你不了解它,你可能会在你的可用内存资源变得越来越少的时候极度郁闷。为了演示这个问题,我们将通过重写Script元素中的内容来引发大量内存的"泄漏"。
循环引用:
<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>
产生的原因:script engines handle circular references through their garbage collectors, but certain unknowns can prevent their heuristics from working properly.
内部函数引用:
<html> <head> <script language="JScript"> function AttachEvents(element) { // This structure causes element to ref ClickEventHandler element.attachEvent("onclick", ClickEventHandler); function ClickEventHandler() { // This closure refs element } } function SetupLeak() { // The leak happens all at once AttachEvents(document.getElementById("LeakedDiv")); } function BreakLeak() { } </script> </head\> <body onload="SetupLeak()" onunload="BreakLeak()"> <div id="LeakedDiv"></div> </body> </html>
页面交叉泄露:
<html> <head> <script language="JScript"> function LeakMemory() { 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() { 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>
而大多数情况下,并不会使用上面的这种方法去追加DOM节点(需要绑定事件的)
document.createElement("<div onClick='foo()'>");
通常是document.createElement,然后再使用绑定,但上面这个有事件在里面。保持自己的怀疑态度。
最后一种
Pseudo-Leaks,也可以称之为伪泄露,而只有部分DOM元素会出现这种情况。
<html> <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() { }"; } } </script> </head> <body> <button onclick="LeakMemory()">Memory Leaking Insert</button> <script id="hostElement">function foo() { }</script> </body> </html>
所以,我觉得上面的一些例子并不是十分符合实际开发中的一些写法和规范(如监听onclck事件的方法);只是如果你不小心在代码中写下与上面相似的代码,那么它就可能已经产生内存泄露了。
分析一些内存泄露的例子:
<html> <head> <script type="text/javascript"> function LeakMemory(){ var parentDiv = document.createElement("<div onclick='foo()'>"); parentDiv.bigString = new Array(1000).join( new Array(2000).join("XXXXX")); } </script> </head> <body> <input type="button" value="Memory Leaking Insert" onclick="LeakMemory()" /> </body> </html>
内存好像没发生什么变化?但确实发生内存泄露,为什么,因为有onclick='foo()'
何以证明?
<html> <head> <script type="text/javascript"> function LeakMemory(){ for(i = 0; i < 5000; i++){ var parentDiv = document.createElement("<div onClick='foo()'>"); } } </script> </head> <body> <input type="button" value="Memory Leaking Insert" onclick="LeakMemory()" /> </body> </html>
<html> <head> <script type="text/javascript"> function LeakMemory(){ for(i = 0; i < 50000; i++){ var parentDiv = document.createElement("div"); } } </script> </head> <body> <input type="button" value="Memory Leaking Insert" onclick="LeakMemory()" /> </body> </html>
比较上面的两段代码,会发现仅仅是第一段比第二段多了一个内联脚本对象(onclick=’foo()’),它没有被正确的释放。
下面的代码也会产生内存泄露的问题:
<html> <head> <script type="text/javascript"> function LeakMemory(){ var parentDiv = document.createElement("div"); parentDiv.onclick=function(){ foo(); }; parentDiv.bigString = new Array(1000).join(new Array(2000).join("XXXXX")); } </script> </head> <body> <input type="button" value="Memory Leaking Insert" onclick="LeakMemory()" /> </body> </html>
因为onclick后面的function () {}能对parentDiv进行引用
更多循环引用的例子,如下图:
<html> <head> <script type="text/javascript"> var myGlobalObject; function SetupLeak(){ //Here a reference created from the JS World //to the DOM world. myGlobalObject=document.getElementById("LeakedDiv"); //Here DOM refers back to JS World; //hence a circular reference. //The memory will leak if not handled properly. document.getElementById("LeakedDiv").expandoProperty= myGlobalObject; } </script> </head> <body onload="SetupLeak()"> <div id="LeakedDiv"></div> </body> </html>
<html> <head> <script type="text/javascript"> window.onload=function(){ // obj will be gc'ed as soon as // it goes out of scope therefore no leak. var obj = document.getElementById("element"); // this creates a closure over "element" // and will leak if not handled properly. obj.onclick=function(evt){ ... logic ... }; }; </script> </head> <body> <div id="element"></div> </body> </html>
改为下面的写法就不会产生内存泄露了
<html> <head> <script type="text/javascript"> window.onload=function(){ // obj will be gc'ed as soon as // it goes out of scope therefore no leak. var obj = document.getElementById("element"); obj.onclick=element_click; }; //HTML DOM object "element" refers to this function //externally function element_click(evt){ ... logic ... } </script> </head> <body> <div id="element"></div> </body> </html>
虽然IE有这么多的问题,但还是有工具可以检测你写的代码是否存在内存泄露,对于代码量少、复杂度并不高的可以使用sIEve,大项目中使用它想跟踪产生内存泄露的代码则比较困难了。好在还有一个工具:Javascript Leaks Detector
JLD的强大之处在于能够模拟IE6和IE7的GC情况,和真实的回收情况。这样可以做一个比较。
msdn上官方Blog地址:blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-leak-detector-v2.aspx
下载地址:http://joinmicrosofteurope.com/files/IEJSLeaksDetector2.0.1.1.zip
关于 Javascript Closures 可以点击这里查看
jibbering.com/faq/notes/closures/ (英文原文)
www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html 中文译文