JS中哪些操作会造成内存泄露?
内存泄漏:指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。
1、JS的回收机制
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。
到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。
2、标记清除
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
- function test(){
- var a=10;//被标记,进入环境
- var b=20;//被标记,进入环境
- }
- test();//执行完毕之后a、b又被标记离开环境,被回收
3、引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值(function object array)赋给该变量时,则这个值的引用次数就是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
- }
4、哪些操作会造成内存泄露
1)意外的全局变量引起的内存泄露
- function leak(){
- leak="xxx";//leak成为一个全局变量,不会被回收
- }
- function bindEvent(){
- var obj=document.createElement("XXX");
- obj.onclick=function(){
- //Even if it's a empty function
- }
- }
解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
- //将事件处理函数定义在外部
- function onclickHandler(){
- //do something
- }
- function bindEvent(){
- var obj=document.createElement("XXX");
- obj.onclick=onclickHandler;
- }
- //在定义事件处理函数的外部函数中,删除对dom的引用
- function bindEvent(){
- var obj=document.createElement("XXX");
- obj.onclick=function(){
- //Even if it's a empty function
- }
- obj=null;
- }
- 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'))
- }
- var someResouce=getData();
- setInterval(function(){
- var node=document.getElementById('Node');
- if(node){
- node.innerHTML=JSON.stringify(someResouce)
- }
- },1000)
5)子元素存在引起的内存泄露
黄色是指直接被 js变量所引用,在内存里,红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。
6)IE7/8引用计数使用循环引用产生的问题
- function fn(){
- var a={};
- var b={};
- a.pro=b;
- b.pro=a;
- }
- fn();
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;
看上面的例子,有人会觉得太弱了,谁会做这样无聊的事情,但是其实我们经常会这样做
- window.onload=function outerFunction(){
- var obj=document.getElementById("element"):
- obj.onclick=function innerFunction(){};
- };
最简单的解决方式就是自己手工解除循环引用,比如刚才的函数可以这样
- myObject.element=null;
- element.o=null;
- window.onload=function outerFunction(){
- var obj=document.getElementById("element"):
- obj.onclick=function innerFunction(){};
- obj=null;
- };
5、如何分析内存的使用情况
Google Chrome浏览器提供了非常强大的JS调试工具,Memory 视图 profiles 视图让你可以对 JavaScript 代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是 summary 列表和 comparison 列表。 summary 视图提供了不同类型的分配对象以及它们的合计大小:shallow size (一个特定类型的所有对象的总和)和 retained size (shallow size 加上保留此对象的其它对象的大小)。distance 显示了对象到达 GC 根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。 comparison 视图提供了同样的信息但是允许对比不同的快照。这对于找到泄漏很有帮助。
6、怎样避免内存泄露
1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
2)注意程序逻辑,避免“死循环”之类的 ;
3)避免创建过多的对象 原则:不用了的东西要及时归还。