浏览器的垃圾回收机制(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 = 100let 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