1.IE7/8 DOM对象或者ActiveX对象循环引用导致内存泄漏
循环引用分为两种:
第一种:多个对象循环引用
var a=new Object;
var b=new Object;
a.r=b;
b.r=a;
第二种:循环引用自己
var a=new Object;
a.r=a;
对于ECMAScript 对象而言,只要没有其他对象引用对象 a、b,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理。
但是,在 IE7、IE8 中,如果循环引用中的任何对象是 DOM 节点或者 ActiveX 对象,比如var a = document.getElementById("#a"),垃圾收集系统则不会发现它们之间的循环关系,因为IE的DOM回收机制和JS回收机制不是同一个。js回收机制分两种:标记清除和引用计数,引用计数对循环引用的垃圾回收会出现内存泄漏,而IE的DOM回收机制便是采用引用计数的。IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变。
下面摘自小苹果的跟我学习javascript的垃圾回收机制与内存管理
二、标记清除
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
function test(){
var a = 10 ; //被标记 ,进入环境
var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后 a、b又被标离开环境,被回收。
垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
三、引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
function test(){
var a = {} ; //a的引用次数为0
var b = a ; //a的引用次数加1,为1
var c =a; //a的引用次数再加1,为2
var b ={}; //a的引用次数减1,为1
}
Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。
function fn() {
var a = {};
var b = {};
a.pro = b;
b.pro = a;
} fn();
以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。
我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。
看上面的例子,有同学回觉得太弱了,谁会做这样无聊的事情,其实我们是不是就在做
window.onload=function outerFunction(){
var obj = document.getElementById("element");
obj.onclick=function innerFunction(){};
};
这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中的变量,自然也包括obj,是不是很隐蔽啊。
解决办法
最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样
myObject.element = null;
element.o = null; window.onload=function outerFunction(){
var obj = document.getElementById("element");
obj.onclick=function innerFunction(){};
obj=null;
};
将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。
要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变
在上面描述的循环引用例子
window.onload=function outerFunction(){
var obj = document.getElementById("element");
obj.onclick=function innerFunction(){};
};
还可以理解成闭包循环引用导致的内存泄漏。怎么理解?
首先obj是外部的一个对象, obj.onclick定义的这个函数隐式的调用到了obj这个对象(obj.onclick函数中的this就是对象obj)。然后我们需要知道obj.onclick实际上是一个outerFunction外部的函数,为什么?DOM监听事件不可能是局部作用域的,是全局作用域的,明白了吧。所以DOM触发这个事件相当于是在函数outerFunction外部调用了obj.click(),而事件内部使用了outerFunction的变量obj,这就形成了一个闭包。IE7/IE8 DOM的引用计数永远无法回收这个DOM对象。无论如何,这都是DOM循环引用导致的内存泄漏,普通闭包是不会导致内存泄漏的。
改成如下结构
window.onload=function outerFunction(){
var obj = document.getElementById("element");
$(obj).click(function innerFunction(){});
};
jQuery绑定事件最终都没有直接绑定到DOM对象上,而是使用jQuery缓存来绑定的。详见jQuery事件体系结构
即使此时仍然会创建一个闭包,并且也会导致同前面一样的循环,但这里的代码却不会使 IE 发生内存泄漏。由于jQuery考虑到了内存泄漏的潜在危害,所以它会手动释放自己指定的所有事件处理程序(jQuery源代码$.fn.remove函数中有对节点的缓存释放的处理)。只要坚持使用jQuery的事件绑定方法,就无需为这种特定的常见原因导致的内存泄漏而担心。
但是,这并不意味着我们完全脱离了险境。当对DOM元素进行其他操作时,仍然要处处留心。只要是将JavaScript对象指定给DOM元素,就可能在旧版本IE中导致内存泄漏。jQuery只是有助于减少发生这种情况的可能性。
有鉴于此,jQuery为我们提供了另一个避免这种泄漏的工具。用.data()方法,将信息附加到DOM元素。由于这里的数据并非直接保存在扩展属性中(jQuery使用一个内部对象并通过它创建的ID来保存这里所说的数据),因此永远也不会构成引用循环,从而有效回避了内存泄漏问题。这种方式也就是jQuery事件绑定使用的方式。
今天学习《了不起的node.js》一书,看到里面有闭包的描述,发现和之前理解的不一样,然后又搜索资料学习了一下。然后更坚定了我的理解,
简单来说,闭包是执行了a函数以后,正常来说a函数的局部变量等应该能释放,但是由于闭包导致可以继续维持a函数内部的变量不被释放。举个例子
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c=a();//执行完成以后a函数里面的i变量不能被释放,因为有闭包的维持。当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
c();
======》写于2017.10.25
2.基础的DOM泄漏
当原有的DOM被移除时,子结点引用没有被移除则无法回收。
var select = document.querySelector;
var treeRef = select('#tree'); var leafRef = select('#leaf'); //在COM树中leafRef是treeFre的一个子结点 select('body').removeChild(treeRef);//#tree不能被回收入,因为treeRef还在
解决方法:
treeRef = null;//tree还不能被回收,因为叶子结果leafRef还在
leafRef = null;//现在#tree可以被释放了
DOM 插入顺序导致内存泄漏
当 动态创建的2 个不同范围的 DOM 对象附加到一起的时候,一个临时的对象会被创建。这个 DOM 对象改变范围到 document 时,那个临时对象就没用了,这个临时对象没有被回收将导致内存泄漏。如果我们一一将这两个DOM添加到原有的DOM 对象上就不会产生中间临时对象。详见理解和解决IE内存泄漏的页面交叉泄露 Cross-Page Leaks。
3.timer定时器泄漏
var val = 0;
for (var i = 0; i < 90000; i++) {
var buggyObject = {
callAgain: function() {
var ref = this;
val = setTimeout(function() {
ref.callAgain();
}, 90000);
}
}
buggyObject.callAgain();
这个时候你无法回收buggyObject
//虽然你想回收但是timer还在
buggyObject = null;
解决办法,先停止timer然后再回收
//解决方法,先停止定时器
clearTimeout(val);
buggyObject = null;
推荐内存泄漏文章:
理解和解决IE内存泄漏(中文翻译):http://www.tuicool.com/articles/2AZ3y2
理解和解决IE内存泄漏(英文原版):https://msdn.microsoft.com/en-us/library/bb250448.aspx
js内存泄露的几种情况:http://blog.csdn.net/li2274221/article/details/25217297
如果觉得本文不错,请点击右下方【推荐】!