我所理解的IE内存泄露
我所理解的IE内存泄露
最近在做一些web方面的东西,突然发现IE在removeChild一个元素并释放了其dom对象的引用之后,在任务管理器中并不能将内存释放. 这让我相当郁闷, Google一翻,各种内存泄露的说法,看的我头晕。最终还是有些收获。
测试1:
1.不泄露!
function test2(){
gb = document.getElementById('garbageBin');
var i = 0;
while(i++<10000){
var o = document.createElement("<div onclick='foo();'>");
gb.appendChild(o);
gb.removeChild(o);
}
}
2 或者不当即removeChild, 而是过后在另外一个函数中 gb.innerHTML=””
也不泄露。也就是,在刷新之前,可以把内存释放出来。
3 如果即不remove又不 把gb置空, 则刷新也要不回内存.
4. 如果只是var o = document.createElement("<div onclick='foo();'>"); 其他什么也不做,
同样导致泄露。
以上测试在IE7。IE6等会再说。 这告诉我们,如果你在创建元素的时候已经关联了事件处理对象,也就是内联的foo(), 那么一定要将元素挂载到页面中的dom树上, 这样你才有可能通过remove或者innerHTML=’’的方式回收内存。
我到现在对于内存泄露还有疑惑,到底怎样才算泄露?
是刷新页面之前可以把内存收回才算不泄露,还是刷新之后可以收回内存就算不泄露?
前辈们给出的MSDN上那个父子div插入顺序的例子, 我在IE7中跑, 似乎不像网上说的那样,结果是有泄露的那个函数,运行完刷新浏览器,内存被回收了。而第二个函数内存一直没长。 这样理解的话,应该是浏览器刷新之前就被回收(不上涨)就算不泄露吧。那么刷新之后还无法收回的算什么呢?更厉害的内存泄露?呵呵。我晕了。
暂且认为:如果在浏览器刷新之前, 内存可以被回收,才算不泄露。
得出:
规则1:创建的元素,如果包含内联脚本(这是这条规则的前提情况),一定要挂载在dom树上!不能先挂载到不在树上的元素!
如果不包含内联脚本对象, 则不会泄露。
如果包含了内联脚本,则记得先挂在树上~ 哈哈 也就是先把父节点挂载,然后挂载子节点到父节点.
再者说来,在FF等其他浏览器中,根本不允许
document.createElement("<div onclick='foo();'>"); 这样的代码出现么!
所以按照标准的写法来做是有好处的!
var o =document.createElement(“div”)
o.onclick = foo;
这样就不会有泄露!
规则2 不讨论内联事件对象的情况,因为那是个特例, 来看这样的情况
function test(){
gb = document.getElementById('garbageBin');
var i = 0;
while(i++<5000){
(function(){
var o = document.createElement("div");
//产生循环引用!
o.onclick = function(){
alert('haha')
}
gb.appendChild(o)
//虽然remove了元素,由于循环引用的存在,无法回收内存
//应当在remove之前,打破循环引用!
//o.onclick = null; //break!
gb.removeChild(o)
})();
}
}
注意哈,这此会导致内存泄露,就像前辈们说的那样, o.onclick = function一句产生了dom元素和function对象的循环引用。就算挂载到dom,然后remove,内存依然不能收回。(似乎能收回一些,但是仍然没有回到调用前的数值), PF使用率成上升趋势.
当然解决方法很简单, 只要在 removeChild 之前将循环打破即可. 使用o.onclick = null 即可,或者事件响应写在函数外面。这个网上有很多讲,不多说。我关注的其实不是这个,主要是, IE7的removeChild到底能做些什么? 刷新页面的时候,回收的又是哪些内存?
(如果不append也不remove,则会彻底的泄露,刷新无济于事.)
我将红色的部分,也就是内部包装的function去掉。循环5000次,内存没有增长! 我做这样的猜测:内存泄露是由于循环引用没错,然而onclick 关联的那个function 属于匿名函数哈,在整个过程中只解析一次,因此无论你循环多少次,泄露的都只是跟这一个对象有关,因此泄露数量很少很少,以至于看不出。
然而包装成函数调用5000次呢?就生成了5000个onclick关联的function, 泄露的内容就增大了5000倍,因此我们可以看的出来。
3
下面测试正常的创建元素。 先测试只创建, 不append到树上的情况.
//在IE7下没有问题,内存不会增长,说明IE7可以动态回收不在结点树上的并且没有关//联JS对象, 且没有其他对象引用它的元素.
function test2(){
var i = 0;
while(i++<5000){
//(function(){
var o = document.createElement("<div>");
o.innerHTML = "AAA";
//加上下面两句也没问题!
buf.push(o);//如果后面不做处理,则不会被回收哦
buf.pop(); //直接pop掉,也就没有对象引用这个元素了
//})();
//buf.pop 放到这里也完全OK,内存不会增长
}
}
也就是说,定义一个dom元素,只要没有循环引用, 不append到dom树上也没问题。只要没有被引用到,就会被垃圾回收。
这几个例子都是创建一个就删除一个,那么如果先缓存下来,然后再删除呢?
看这个例子:
var i = 0;
while(i++<5000){
(function(){
var o = document.createElement("<div>");
o.onclick = foo;
o.innerHTML = "AAA";
buf.push(o);//如果后面不做处理,则不会被回收哦
})();
}
//直接清空buf
buf.length = 0;
/*
//尝试逐个置空方法
for(var i=0;i<buf.length;i++){
//document.body.removeChild(buf[i]);
buf[i] = null;
}
*/
//尝试pop
//while(buf.pop());
该例子先将5000个对象存在buf里,然后再尝试各种方法去释放他们,也就是断开对他们的引用。 然而实验结果是: 占用较大的内存,而且每次调用这个函数,PF使用率都是相同的! 这说明,内存不会累计增加。 然而也不会立即的释放。虽然PF使用率数值没变,但是由于再次调用还是占用这些,说明之前占用的内存被新的内容覆盖了。然而总体情况,还是没有得到理想的释放效果。 很让人郁闷,为什么创建一个就释放一个就可以,创建多个然后再释放就不完全了呢?
然而这种情况只是在测试,毕竟没人会去创建一堆不挂载到页面dom的元素吧~
再来看挂载到dom树上的情况:
下面的例子也OK
while(i++<5000){
(function(){
var o = document.createElement("<div>");
o.onclick = foo; //因为定义在了外部,没有循环引用问题
o.innerHTML = "AAA";
document.body.appendChild(o);
document.body.removeChild(o);
})();
}
如果去掉removeChild一行,内存情况也尚好,只是增加一点,这是正常的,毕竟显示到页面上需要占用内存。 然而让我不解的是: 为什么append到页面就占用很少内存,而保存
到buf里面就会占用很大内存呢??不解!
而且,如果先append,然后保存到buf里,占用内存依然很少!
还有如果不设置innerHTML, 则占用内存极少! 里面的原因我就猜不到了.
我只能得出这样的结论: IE中,创建一个元素,务必把它挂载到dom树上! 这样即使不remove,刷新后内存也会释放。
如果将删除代码移到function外面, 情况一样,没有泄露。
再来实验将5000个对象挂载后,然后再删除的情况
我们通过buf保存对象的引用:
while(i++<10000){
var o;
(function(){
o = document.createElement("<div>");
o.onclick = foo;
o.innerHTML = "haha";
o.style.left="10px";
document.body.appendChild(o);
buf.push(o);//保存引用
})();
}
// 这里调用removeChild来释放
while(buf.length>0){
document.body.removeChild(buf.pop());
}
测试结果还不错,虽然内存会上升,但是多次调用会维持在一个水平上,这说明,每次调用时,新的对象占用旧的对象的内存,因此不会累计增加,还算OK.
结论是:同未挂载的情况类似,如果一次性缓存多个对象然后统一removeChild来清除,则IE不会立即释放内存,如果有新的变量或者对象出现,则会覆盖那部分内存。总的情况还不差。
使用另一种方式删除:定义一个看不见的元素gb当做垃圾站
/*
while(buf.length>0){
//首先说明,一个元素只能挂载到一个点上,因为dom是一个树结构,
//元素对象只是将指针挂载到树的某个位置,之前对象在body上,现在挂载到了
//gb上,那么body中就不显示了,而转到gb上来,
//另外如果将一个元素innerHTML置空,意味着其子元素的内存被释放!
//所以,这是一个名副其实的回收站~哈哈
gb.appendChild(buf.pop());
gb.innerHTML = "";
}
*/
这种情况得到的结果和上面类似,然而内存占用更少,上面是35M左右,而这个维持在25M左右,情况好了不少。 只是效率低了一点,因为有append操作
其实如果不做清除的时候,内存占用到了25M. 而清除之后,第一种方法35M,第二种方法25M, 似乎根本没有清除,其实还是这样,新的内容会覆盖旧的内存,只是任务管理器没有显示出来。因为如果不清除,多次调用后内存是累计增加的,而清除后会维持在一个水平。
还有pop的速度很慢,改成for会快很多!
var l=buf.length;
for(var i=0;i<l;i++){
gb.appendChild(buf[i]);
}
gb.innerHTML = ""; //置空,子元素全被释放!
buf.length = 0; //重置buf
所以,使用这种方法清除元素是再好不过的了。 可是需要注意的是,如果内部元素有循环引用的现象,清除之前一定要先把循环引用断开,方法就是递归的清除类型为function的属性。
只要有善于发现循环引用的良好习惯~ 问题就不是问题了~