JS哪些操作会造成内存泄漏?
1.背景介绍
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。在C++中,因为是手动管理内存,内存泄露是经常出现的事情。而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露。浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露。
2.知识剖析
js的回收机制:垃圾回收机制—GC
Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。
1、标记清除
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
function test(){
var a = 10 ; //被标记 ,进入环境
var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后a、b又被标离开环境,被回收。
2、引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是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
}
3.常见问题
JS哪些操作会造成内存泄漏?
4.解决方案
虽然JavaScript会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。下面列一下内存泄露常见的几种情况。
1、意外的全局变量引起的内存泄漏
function leaks(){
leak = 'xxxxxx';//leak成为一个全局变量,不会被回收
}
2、闭包引起的内存泄漏
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
}
闭包可以维持函数内局部变量,使其得不到释放。上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包,解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用
//将事件处理函数定义在外部
function bindEvent() {
var obj=document.createElement("XXX");
obj.onclick=onclickHandler;
}
function onclickHandler(){
//do something
}
//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
obj=null;
}
3、没有清理的DOM元素
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
}
function removeButton() {
document.body.removeChild(document.getElementById('button'));
}
虽然我们用removeChild移除了button,但是还在elements对象里保存着#button的引用,换言之, DOM元素还在内存里面。
4、被遗忘的定时器或者回调
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
这样的代码很常见,如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放。
5、子元素存在引用引起的内存泄漏
黄色是指直接被js变量所引用,在内存里
红色是指间接被js变量所引用,如上图,refB被refA间接引用,导致即使refB变量被清空,也是不会被回收的
子元素refB由于parentNode的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除
5.编码实战
6.扩展思考
IE7/8引用计数使用循环引用产生的问题。
function fn() {
var a = {};
var b = {};
a.pro = b;
b.pro = a;
}
fn();
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的回收方式已经改变
7.参考文献
参考一:javascript的垃圾回收机制与内存管理http://www.jb51.net/article/75292.htm
参考二:js内存泄漏常见的四种情况http://www.tuicool.com/articles/7J3amu
8.更多讨论
如何分析JS内存使用
Google Chrome浏览器提供了非常强大的JS调试工具,Memory视图
profiles视图让你可以对JavaScript代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是summary列表和comparison列表。
summary视图提供了不同类型的分配对象以及它们的合计大小:shallow size(一个特定类型的所有对象的总和)和retained size(shallow size加上保留此对象的其它对象的大小)。distance显示了对象到达GC根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。
comparison视图提供了同样的信息但是允许对比不同的快照。这对于找到泄露很有帮助。
JS内存泄漏排查方法---http://www.2cto.com/kf/201402/281855.html
问题:1、全局变量如何清除。
2、垃圾回收的机制:是根据什么来决定是否清除的。
PPT地址:https://ptteng.github.io/PPT/PPT/js-11-JS%20memory%20leak.html#/
视频地址:https://v.qq.com/x/page/x0512k9nmxt.html
今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~
下期预告:如何使用gulp?
------------------------------------------------------------------------------------------------------------------------
技能树.IT修真院
“我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。
这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧~
我的邀请码:96194340,或者你可以直接点击此链接:http://www.jnshu.com/login/1/96194340