前端基础知识之 JS 垃圾回收

语言: CN / TW / HK

今天我们来总结一下 JS 的垃圾回收机制。主要阐述了 JS 中垃圾的定义,垃圾回收的算法、缺点以及改进方法等。

什么是垃圾

我们平时生活中没有利用价值的就可以称之为“垃圾”。但在 JS 中,垃圾更多指的是一些不需要的东西(变量、对象等)。而判断一个变量是不是垃圾,我总结了以下几点:

  1. 所有全局变量、window(或 global)对象都不是垃圾
  2. 所有变量都有它的生命周期,局部变量在退出了它的作用域之后,就成了垃圾

而对于局部变量来说,又可以分成以下三种情况:

  1. 单引用 var a = { name: 'frank' }

  2. 双引用 var a= { name: "frank"; } var admin = a 此时如果把 a 变量消掉,但仍有变量会引用它里面的内容,那么就不会成为垃圾

  3. 环引用

``` function marry(man, woman){ woman.husband = man; man.wife = woman;

return {
    father: man,
    mother: woman
}

}

let family = marry({ name: "John" },{ name: "Ann" }) ```

那么全局变量 family 分别有father John和 mother Ann两个引用,而man 和 woman 之间又用 wife 和 husband 相互引用,形成了环;

那么把 father 和 husband 都删掉,father也不会成为垃圾,因为 father 会成为垃圾,即使它的wife引用还在,但是引用别人没用。

所有指向它的引用都删掉才行

如果把 window 不指向这个 family,即使家庭和father、wife互相引用,但这个环就整个成为垃圾了

垃圾回收算法

这里简要说一说几种常见的 JS 垃圾回收算法的原理,但是并没有展示源代码

  1. 标记清除算法 mark-swipe

从global开始去找每一个箭头,有人用的就标记,不能删除, 然后遍历每一个标记的对象,全部勾, 发现没有新的对象了,然后看没有标记的,全都删除

这种算法有一个显著缺点,那就是当对象很多的时候,由于 JS 是单线程的缘故,垃圾回收速度会明显变慢,并且如果在执行过程中有另外的 JS 代码需要执行,那么就会中断。

那么该如何改进呢?

  • 可以采用分代回收的方法,讲标记的对象分为新一代和老一代,分代回收
  • 增量收集,按照时间顺序,先检查一批变量后,执行一段时间 JS 代码,然后再继续检查
  • 空闲时间收集,等到不需要执行 JS 代码时再收集

但是对于前端来说,还有DOM 进程这个 “大 BOSS”:

``` var div = document.getElementById('xxx');

div.onclick = function(){} //函数不应该被当作垃圾

setTimeout(function(){ div.remove() //只从页面中被删掉,但是内存中还有 },3000)


var div = document.getElementById('xxx');//DOM并没有被删除,只是变量被删了

div.onclick = function(){} //函数也不应该被当作垃圾

setTimeout(function(){ div = null //只是 div这个对象等于空了,但是html 里面的div还在页面中! },3000) ```

对于以上代码,让 div 这个变量如果先 remove 再让它等于 null,就可以把 onclick 函数回收掉,否则无法回收

但是 IE 又有 bug,它在 onclick 函数存在时就认为 DOM 会不被引用,所以即使先 remove 再 null 也无法回收,只能手动让 onclick = null

  1. 引用计数算法 reference counting

  2. 计数

  3. 比如 所有全局变量都标为1 每次生成一个新对象就记1,引用一次就加1,不引用就 -1

  4. 只要变量不为0,就不回收

  5. 当计数为0时就回收

这种算法不需要扫描,可以即刻回收变量,但是也有缺点:

增数器的处理工作繁重,而且对于循环引用来说,没有办法计数

  1. 标记压缩算法

这种算法会利用堆进行标记压缩,由于比较复杂,这里就不过多阐述,感兴趣的话可以看一看下面这张示意图:

image.png

©本总结教程版权归作者所有,转载需注明出处