关于javascript:JS垃圾回收机制

40次阅读

共计 2674 个字符,预计需要花费 7 分钟才能阅读完成。

浏览器的垃圾回收机制(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. 而后它遍历并“标记”来自它们的所有援用。
  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.org、javascript.info

正文完
 0