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(){
js= "xxx"; //js成为一个全局变量,不会被回收
}

2)闭包引起的内存泄露

?function bindEvent(){
var obj= document.createElement( "XXX");
obj.οnclick= 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.οnclick= 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‘))

}

4)被遗忘的定时器或者回调

 

?var someResouce=getData();

setInterval( function(){

var node= document.getElementById( ‘Node‘);

if(node){

node.innerHTML= JSON.stringify(someResouce)

}

}, 1000)

这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

5)子元素存在引起的内存泄露

JS哪些操作会造成内存泄露?

 

黄色是指直接被 js变量所引用,在内存里,红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。

 

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有一个名为e的属性指向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.οnlοad= function outerFunction(){

var obj= document.getElementById( "element"):

obj.οnclick= function innerFunction(){};

obj= null;

};

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。 要注意的是,IE9+并不存在循环引用导致Dom内存泄漏问题,可能是微软做了优化,或者Dom的回收方式已经改变

 

5、如何分析内存的使用情况

Google Chrome浏览器提供了非常强大的JS调试工具,Memory 视图 profiles 视图让你可以对 JavaScript 代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是 summary 列表和 comparison 列表。 summary 视图提供了不同类型的分配对象以及它们的合计大小:shallow size (一个特定类型的所有对象的总和)和 retained size (shallow size 加上保留此对象的其它对象的大小)。distance 显示了对象到达 GC 根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。 comparison 视图提供了同样的信息但是允许对比不同的快照。这对于找到泄漏很有帮助。

 

6、怎样避免内存泄露

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象 原则:不用了的东西要及时归还。

内存泄漏:指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。

 

1、JS的回收机制

JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。

到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除引用计数。引用计数不太常用,标记清除较为常用。

 

2、标记清除

js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

 

  1.  
    function test(){
  2.  
    var a= 10//被标记,进入环境
  3.  
    var b= 20//被标记,进入环境
  4.  
    }
  5.  
    test(); //执行完毕之后a、b又被标记离开环境,被回收


3、引用此时

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值(function object array)赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

 

  1.  
    function test(){
  2.  
    var a={}; //a的引用次数为0
  3.  
    var b=a; //a的引用次数加1,为1
  4.  
    var c=a; //a的引用次数加1,为2
  5.  
    var b={}; //a的引用次数减1,为1
  6.  
    }


4、哪些操作会造成内存泄露

1)意外的全局变量引起的内存泄露

 

  1.  
    function leak(){
  2.  
    leak= "xxx"//leak成为一个全局变量,不会被回收
  3.  
    }


2)闭包引起的内存泄露

 

  1.  
    function bindEvent(){
  2.  
    var obj= document.createElement( "XXX");
  3.  
    obj.οnclick= function(){
  4.  
    //Even if it‘s a empty function
  5.  
    }
  6.  
    }

闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。

解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

  1.  
    //将事件处理函数定义在外部
  2.  
    function onclickHandler(){
  3.  
    //do something
  4.  
    }
  5.  
    function bindEvent(){
  6.  
    var obj= document.createElement( "XXX");
  7.  
    obj.onclick=onclickHandler;
  8.  
    }

 

  1.  
    //在定义事件处理函数的外部函数中,删除对dom的引用
  2.  
    function bindEvent(){
  3.  
    var obj= document.createElement( "XXX");
  4.  
    obj.οnclick= function(){
  5.  
    //Even if it‘s a empty function
  6.  
    }
  7.  
    obj= null;
  8.  
    }


3)没有清理的DOM元素引用

 

  1.  
    var elements={
  2.  
    buttondocument.getElementById( "button"),
  3.  
    imagedocument.getElementById( "image"),
  4.  
    textdocument.getElementById( "text")
  5.  
    };
  6.  
    function doStuff(){
  7.  
    image.src= "http://some.url/image";
  8.  
    button.click():
  9.  
    console.log(text.innerHTML)
  10.  
    }
  11.  
    function removeButton(){
  12.  
    document.body.removeChild( document.getElementById( ‘button‘))
  13.  
    }


4)被遗忘的定时器或者回调

 

  1.  
    var someResouce=getData();
  2.  
    setInterval( function(){
  3.  
    var node= document.getElementById( ‘Node‘);
  4.  
    if(node){
  5.  
    node.innerHTML= JSON.stringify(someResouce)
  6.  
    }
  7.  
    }, 1000)

这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。


5)子元素存在引起的内存泄露

JS哪些操作会造成内存泄露?

 

黄色是指直接被 js变量所引用,在内存里,红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。

 

6)IE7/8引用计数使用循环引用产生的问题

 

  1.  
    function fn(){
  2.  
    var a={};
  3.  
    var b={};
  4.  
    a.pro=b;
  5.  
    b.pro=a;
  6.  
    }
  7.  
    fn();

fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄漏。在IE7与IE8上,内存直线上升。

IE中有一部分对象并不是原生js对象。例如,其内存泄漏DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

 

  1.  
    var element= document.getElementById( "some_element");
  2.  
    var myObject= new Object();
  3.  
    myObject.e=element;
  4.  
    element.o=myObject;

上面的例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为e的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

看上面的例子,有人会觉得太弱了,谁会做这样无聊的事情,但是其实我们经常会这样做

 

  1.  
    window.onload= function outerFunction(){
  2.  
    var obj= document.getElementById( "element"):
  3.  
    obj.onclick= function innerFunction(){};
  4.  
    };

这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中的变量,自然也包括obj,是不是很隐蔽啊。

最简单的解决方式就是自己手工解除循环引用,比如刚才的函数可以这样

  1.  
    myObject.element= null;
  2.  
    element.o= null;
  3.  
    window.οnlοad= function outerFunction(){
  4.  
    var obj= document.getElementById( "element"):
  5.  
    obj.οnclick= function innerFunction(){};
  6.  
    obj= null;
  7.  
    };

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。 要注意的是,IE9+并不存在循环引用导致Dom内存泄漏问题,可能是微软做了优化,或者Dom的回收方式已经改变

5、如何分析内存的使用情况

Google Chrome浏览器提供了非常强大的JS调试工具,Memory 视图  profiles 视图让你可以对 JavaScript 代码运行时的内存进行快照,并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表,但是对我们最有用的是 summary 列表和 comparison 列表。  summary 视图提供了不同类型的分配对象以及它们的合计大小:shallow size (一个特定类型的所有对象的总和)和 retained size (shallow size 加上保留此对象的其它对象的大小)。distance 显示了对象到达 GC 根(校者注:最初引用的那块内存,具体内容可自行搜索该术语)的最短距离。 comparison 视图提供了同样的信息但是允许对比不同的快照。这对于找到泄漏很有帮助。

 

6、怎样避免内存泄露

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象  原则:不用了的东西要及时归还。

JS哪些操作会造成内存泄露?

上一篇:JS断点调试


下一篇:vue error code ETIMEDOUT,network tunneling socket could not be established, cause=connect ETIMEDOUT