前端基础知识之 JS 垃圾回收
今天我们来总结一下 JS 的垃圾回收机制。主要阐述了 JS 中垃圾的定义,垃圾回收的算法、缺点以及改进方法等。
什么是垃圾
我们平时生活中没有利用价值的就可以称之为“垃圾”。但在 JS 中,垃圾更多指的是一些不需要的东西(变量、对象等)。而判断一个变量是不是垃圾,我总结了以下几点:
- 所有全局变量、window(或 global)对象都不是垃圾
- 所有变量都有它的生命周期,局部变量在退出了它的作用域之后,就成了垃圾
而对于局部变量来说,又可以分成以下三种情况:
-
单引用
var a = { name: 'frank' }
-
双引用
var a= { name: "frank"; } var admin = a
此时如果把 a 变量消掉,但仍有变量会引用它里面的内容,那么就不会成为垃圾 -
环引用
``` 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 垃圾回收算法的原理,但是并没有展示源代码
- 标记清除算法 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
-
引用计数算法 reference counting
-
计数
-
比如 所有全局变量都标为1 每次生成一个新对象就记1,引用一次就加1,不引用就 -1
-
只要变量不为0,就不回收
-
当计数为0时就回收
这种算法不需要扫描,可以即刻回收变量,但是也有缺点:
增数器的处理工作繁重,而且对于循环引用来说,没有办法计数
- 标记压缩算法
这种算法会利用堆进行标记压缩,由于比较复杂,这里就不过多阐述,感兴趣的话可以看一看下面这张示意图: