zoukankan      html  css  js  c++  java
  • 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事件绑定使用的方式。

      今天学习《了不起的node.js》一书,看到里面有闭包的描述,发现和之前理解的不一样,然后又搜索资料学习了一下。然后更坚定了我的理解,

      简单来说,闭包是执行了a函数以后,正常来说a函数的局部变量等应该能释放,但是由于闭包导致可以继续维持a函数内部的变量不被释放。举个例子

    function a(){
      var i=0;
      function b(){
        alert(++i);
      }
      return b;
    }
    var c=a();//执行完成以后a函数里面的i变量不能被释放,因为有闭包的维持。当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
    c();

      ======》写于2017.10.25

    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

  • 相关阅读:
    分苹果
    马拉车算法(求最长回文子串)
    KMP
    字典树
    关于子类和父类中的this的用法
    最长上生子序列LIS
    sass
    黑马程序员----java基础笔记下(毕向东)
    DOM
    ajax教程
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/5196330.html
Copyright © 2011-2022 走看看