前端如何处理内存泄漏

Posted ygunoil

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端如何处理内存泄漏相关的知识,希望对你有一定的参考价值。

定义

内存泄漏:应用程序不再需要的内存,由于某种原因,内存没有返回到操作系统或可用内存池中。

原因

1.意外的全局变量

JS 在处理未声明的变量时,对未声明的变量的引用会在全局对象内创建一个新变量。这些全局变量是无法进行垃圾回收的(除非将它们赋值为 null 或重新进行分配),所以对于存储大量数据的全局变量,确保在使用完之后,对其赋值为 null 或者重新分配。

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

2.被遗忘的定时器(Timers)或者回调函数(callback)

  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)
  8.  
    // 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在,
  9.  
    // 而且, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

3.闭包函数

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

 

闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

  1.  
    //将事件处理函数定义在外部
  2.  
    function onclickHandler(){
  3.  
    //do something
  4.  
    }
  5.  
     
  6.  
    function bindEvent(){
  7.  
    var obj=document.createElement("XXX");
  8.  
    obj.onclick=onclickHandler;
  9.  
    }
  10.  
     
  11.  
     
  12.  
    //在定义事件处理函数的外部函数中,删除对dom的引用
  13.  
    function bindEvent(){
  14.  
    var obj=document.createElement("XXX");
  15.  
    obj.onclick=function(){
  16.  
    //Even if it‘s a empty function
  17.  
    }
  18.  
    obj=null;
  19.  
    }
 

4.没有清理的DOM元素的引用

  1.  
    var elements={
  2.  
    button: document.getElementById("button"),
  3.  
    image: document.getElementById("image"),
  4.  
    text: document.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.  
    }

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

技术图片

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

在举个例子,假设我们在 javascript 代码中保留了对 table 特定单元格(<td>)的引用。有一天,我们决定从 DOM 中删除该 table,但仍保留着对该单元格的引用。直观地来看,可以假设 GC 将收集除了该单元格之外所有的内容。实际上,这是不会发生的,因为该单元格是该 table 的子节点,并且 children 保持着对它们 parents 的引用。也就是说,在 JavaScript 代码中对单元格的引用会导致整个表都保留在内存中的。

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

这个问题主要是在于IE7/8的垃圾回收机制,使用的是引用计数法,当两个变量A和B相互引用时,即使应用程序中不再使用这两个变量,GC也不会将这两个变量回收。

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

排查

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

技术图片

右边视图中列出了heap里的对象列表。

  • constructor:类名
  • Distance:对象到根的引用层级距离
  • Objects Count:该类的对象数
  • Shallow Size:对象所占内存(不包含内部引用的其他对象所占的内存)
  • Retained Size:对象所占的总内存(包含····················································)

内存泄漏的排查

技术图片

将上图框框切换到comparison(对照)选项,该视图列出了当前视图与上一个视图的对象差异

  • New:新建了多少对象
  • Deleted:回收了多少对象
  • Delta:新建的对象个数减去回收的对象个数

重点看closure(闭包),如果#Delta为正数,则表示创建了闭包函数,如果多个快照中都没有变负数,则表示没有销毁闭包

以上是关于前端如何处理内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript如何工作 3之 内存管理+如何处理4个常见的内存泄漏

kafka连接器如何处理内存泄漏?

如何处理以设置间隔卸载组件以避免内存泄漏

JavaScript如何工作 3之 内存管理+如何处理4个常见的内存泄漏

How Javascript works (Javascript工作原理) 内存管理及如何处理 4 类常见的内存泄漏问题

片段如何处理触摸?