美文网首页
JS垃圾回收机制

JS垃圾回收机制

作者: 唐_银 | 来源:发表于2020-10-24 11:29 被阅读0次

    浏览器的垃圾回收机制(Garbage collection
    ),简称GC,它会周期性运行以释放那些不需要的内存,否则,JavaScript的解释器将会耗尽全部系统内存而导致系统崩溃。

    具体到浏览器中的实现,通常有两个策略:标记清楚和引用计数。

    引用计数法

    此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

    let car = {
        logo: 'luhu',
        price: 100
    }   // car 是第一个对这个对象的引用
    
    let jeep = car // jeep是第二个对这个对象的引用
    car.logo = null // logo属性虽然设置为null 但此属性还在被jeep引用 不会被回收
    car = '' //  此时这个对象还有有jeep一个引用
    jeep = null // jeep设置为null 那个对象是零引用了 可以被回收了
    
    

    引用计数法是最初级的垃圾收集算法,如果某对象没有其他对象指向它了,那就说明它可以被回收。但是它无法处理循环引用的问题。

    循环引用的限制

    // 引用计数法的限制
    function f(){
      let o1 = {}
      let o2 = {}
      o1.a = o2
      o2.a = o1
    
      return 100
    }
    
    f()
    

    我们执行f函数,它返回了一个数字,和内部的o1,o2没什么关系,但是对引用计数法来说,o1,o2它们之间还存在着相互引用,并不会被回收。这就造成了内存泄漏。

    标记清除法

    这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

    从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。标记清除法假定存在一个根对象(相当于js的全局对象),垃圾回收器将定期从根对象开始查找,凡是从根部出发能扫描到的都会保留,扫描不到的将被回收。

    从根对象开始扫描

    1

    右侧的部分无法到达,它将会被回收

    2

    就像是一桶水我们把它从根对象泼下去,水流过的地方都没事,没沾上水的对象就该回收了。

    内部流程

    1. 垃圾收集器找到所有的根,并“标记”(记住)它们。
    2. 然后它遍历并“标记”来自它们的所有引用。
    3. 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
    4. ……如此操作,直到所有可达的(从根部)引用都被访问到。
    5. 没有被标记的对象都会被删除。

    几种常见的内存泄漏

    1. 全局变量

    全局变量什么时候需要自动释放内存空间很难判断,所以在开发中尽量避免使用全局变量,以提高内存有效使用率。

    1. 未移除的事件绑定

    dom元素虽然被移除了,但元素绑定的事件还在,如果不及时移除事件绑定,在IE9以下版本容易导致内存泄漏。现代浏览器不存在这个问题了,了解一下即可。

    let div = document.querySelector(".div");
    let name = 'lee'
    let handler = function () {
        console.log(name);
    }
    div.addEventListener('click', handler, false)
    
    div.parentNode.removeChild(div) // 在IE9以下的老版本事件还在
    
    1. 无效的dom引用

    有时候将dom作为对象的key存储起来很有用,但是在不需要该dom时,要记得及时解除对它的引用。

    var ele = {
      node: document.getElementById('node')
    };
    
    document.body.removeChild(document.getElementById('node')); // 此时ele中还存在对node的引用
    
    1. 定时器setInterval/setTimeout

    看下面的一段定时器代码,一旦我们在其它地方移除了node节点,定时器的回调便失去了意义,然而它一直在执行导致callback无法回收,进而造成callback内部掉数据resData也无法被回收。所以我们应该及时clear定时器。

    let resData = 100
    let callback = function () {
        let node = document.querySelecter('.p')
        node && (node.innerHTML = resData)
    }
    
    setInterval (callback, 1000)
    

    另外单独说一下闭包,闭包和内存泄漏没有半毛钱关系,只是由于IE9之前的版本垃圾收集机制的原因,导致内存无法进行回收,这是IE的问题,现代浏览器基本都不存在这个问题。当然闭包要是使用不当肯定是会造成内存泄漏。

    WeakMap、WeakSet

    es6的WeakMap和Map类似,都是用于生成键值对的集合,不同的是WeakMap是一种弱引用,它的键名所指向的对象,不计入垃圾回收机制,另外就是WeakMap只接受对象作为键名(null除外),而Map可以接受各种类型的数据作为键。

    WeakMap这种结构有助于防止内存泄漏,一旦消除对键的引用,它占用的内存就会被垃圾回收机制释放。WeakMap 保存的这个键值对,也会自动消失。包括WeakSet也是类似的,内部存储的都是弱引用对象,不会被计入垃圾回收。

    看一个阮一峰ES6文档上举的例子:

    let myWeakmap = new WeakMap();
    
    myWeakmap.set(
      document.getElementById('logo'),
      {timesClicked: 0})
    ;
    
    document.getElementById('logo').addEventListener('click', function() {
      let logoData = myWeakmap.get(document.getElementById('logo'));
      logoData.timesClicked++;
    }, false);
    
    

    上面代码中,我们将dom对象作为键名,每次点击,我们就更新一下状态。我们将这个状态作为键值放在WeakMap里。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险。

    WeakSet和WeakMap类似,它和set结构的区别也是两点:

    1. WeakSet中的对象都是弱引用,不会被计入垃圾回收
    2. 成员只能是对象,而不能是其他类型的值

    所以从垃圾回收的角度来看,合理的使用WeakMap和WeakSet,能帮助我们避免内存泄漏。

    小结

    js的垃圾回收机制我们无法人为干预,浏览器会定期巡查,js引擎在内部做了很多优化,使其可以执行的更快,现代浏览器基本都采用标记清楚的方法进行垃圾回收。了解内存泄漏的原因以及如何去规避,防止翻车事故的发生🐵

    参考资料: developer.mozilla.orgjavascript.info

    相关文章

      网友评论

          本文标题:JS垃圾回收机制

          本文链接:https://www.haomeiwen.com/subject/jhpimktx.html