js晋级篇——前端内存泄漏探讨
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js晋级篇——前端内存泄漏探讨相关的知识,希望对你有一定的参考价值。
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事件绑定使用的方式。
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
如果觉得本文不错,请点击右下方【推荐】!
以上是关于js晋级篇——前端内存泄漏探讨的主要内容,如果未能解决你的问题,请参考以下文章