【转】有关JavaScript内存泄露
前言
内存存泄漏是指分配给应用的内存不能被重新分配,即使在内存已经不被使用的时候。正常情况下,垃圾回收器在DOM元素和event处理器不被引用或访问的时候回收它们。但是,IE的早些版本(IE7和之前)中内存泄漏是很容易出现的,因为内存管理器不能正确理解Javascript生命周期而且在周期被打破(可以通过赋值为null实现)前不会回收内存。
在大型Web应用程序开发中,内存泄露可能会造成严重的性能问题,甚至导致浏览器崩溃。
内存泄露的情形
1. 循环引用
循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有DOM对象或者ActiveX对象时,循环引用将导致内存泄露。此时,dom将不会在脚本停止的时候被垃圾回收器回收。
var dom = document.getElementById("my"); document.getElementById("my").kk = dom;
如果要避免这种情况,引用DOM元素的对象或DOM对象的引用需要被赋值为null。
dom = null;// Or document.getElementById("my").kk = null;
2. JavaScript闭包
闭包可以导致内存泄漏是因为内部方法保持一个对外部方法变量的引用,所以尽管方法返回了内部方法还可以继续访问在外部方法中定义的私有变量。
function bindEvent() { var dom = document.getElementById("my"); dom.onclick = function() {}; } bindEvent();
上面这段代码肯定会发生内存泄露,因为DOM的事件函数是一个内部函数,它可以访问到dom,而dom引用了DOM对象。有两种解决办法,一是把onclick的函数写在外部:
function bindEvent() { var dom = document.getElementById("my"); dom.onclick = onClickHandler; } function onClickHandler() {} bindEvent();
另一个办法是 dom=null:
function bindEvent() { var dom = document.getElementById("my"); dom.onclick = function() {}; dom = null; } bindEvent();
但是,有时候我们不能使用这两种办法,比如如下情况:
function bindEvent() { var dom = document.getElementById("my"); var test = "test value"; dom.onclick = function() { alert(test); }; return dom; } bindEvent();
此时,因为要访问内部变量test,所以onclick的函数不能拿到外部;因为要返回dom,所以也不能将dom=null。我们可以使用如下的两种方式,先来看第一种:
function bindEvent() { var dom = document.getElementById("my"); var test = "test value"; dom.onclick = onClickBuilder(test); return dom; } function onClickBuilder(test) { return function() { alert(test); } } bindEvent();
还有另一种不常用的办法:
function bindEvent() { try { var dom = document.getElementById("my"); var test = "test value"; dom.onclick = function() { alert(test); }; return dom; } finally { dom = null; } } bindEvent();
3. DOM插入顺序
这种基于插入顺序而常常引起的泄漏问题,主要是由于对象创建过程中的临时对象未能被及时清理和释放造成的。它一般在动态创建页面元素,并将其添加到页面DOM中时发生。一个最简单的示例场景是我们动态创建两个对象,并创建一个子元素和父元素间的临时域。然后,当你将这两个父子结构元素构成的的树添加到页面DOM树中时,这两个元素将会继承页面DOM中的层次管理域对象,并泄漏之前创建的那个临时域对象。下面的图示示例了两种动态创建并添加元素到页面DOM中的方法。在第一种方法中,我们将每个子元素添加到它的直接父元素中,最后再将创建好的整棵子树添加到页面DOM中。当一些相关条件合适时,这种方法将会由于临时对象问题引起泄漏。在第二种方法中,我们自顶向下创建动态元素,并使它们被创建后立即加入到页面DOM结构中去。由于每个被加入的元素继承了页面DOM中的结构域对象,我们不需要创建任何的临时域。这是避免潜在内存泄漏发生的好方法。
DOM 对象应该按照从当前页面存在的最上面的 DOM 元素开始往下直到剩下的 DOM 元素的顺序添加,这样它们就总是有同样的范围,不会产生临时对象。仔细阅读如下的代码,并尝试一下,就什么都知道啦。
function LeakMemory() { var hostElement = document.getElementById("hostElement"); // Do it a lot, look at Task Manager for memory response for (var 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 (var 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; }
removeChild容易造成内存泄漏,可以使用innerHTML代替,或者可以考虑覆盖document.createElement()方法。
function MemoryFix() { var garbageBox = document.createElement("div"); garbageBox.style.display = "none"; document.body.appendChild(garbageBox); var createElement = document.createElement; document.createElement = function() { var obj = Function.prototype.apply.apply(createElement, [document, arguments]); garbageBox.appendChild(obj); return obj; } }
4. 自动类型包装转换
如下的代码会造成内存泄漏:
var str = "testtest"; console.log(str.length);
参考
http://birdshome.cnblogs.com/arc ... /IE_MemoryLeak.html