垃圾回收机制
内存泄漏:在程序运行过程中会有一些垃圾数据不再使用,需要及时释放出去,如果我们没有及时释放,就会发生内存泄漏。
JS 中的垃圾数据都是由垃圾回收器(GC)自动回收的,不需要手动释放,它是如何运行的呢?
很简单,JS 引擎中有一个后台进程称为 垃圾回收器,它监视所有对象,观察对象是否可被访问,然后按照固定的时间间隔,周期性的删除掉那些不可访问的对象。
现在各大浏览器通常采用的垃圾回收器有两种方法:
- 引用计数
- 标记清除
引用计数
引用计数 是早期最简单的垃圾回收机制,就是给一个占用物理空间的对象附加一个引用计数器,当有其他对象引用这个对象时,这个对象的引用计数加一,反之解除时就减一,当对象的引用计数为 0 时就会被回收。
下面的示例,就可能会引起内存泄漏:
js
// 循环引用的问题
function temp() {
const a = {}
const b = {}
a.o = b
b.o = a
}
这种情况下,每次调用 temp 函数,a 和 b 的引用计数都是 2 ,会使这部门内存永远不会被释放。
标记清除
V8 中垃圾回收器就是采用 标记清除 进行垃圾回收的,主要流程如下:
- 标记:遍历调用栈,看老生代区域堆中的对象是否被引用,被引用的对象标记为活动对象,没有被引用的对象标记为垃圾数据(待清理)
- 垃圾清理:将所有的垃圾数据清理掉
在开发过程中,如果想要让垃圾回收机制回收某一对象,就将对象的引用设置为 null 即可,如下示例:
js
function createGarbage() {
let largeObject = {
name: "large object",
data: new Array(1000000).fill("Some data")
}
// 将 largeObject 设置为 null,使其可以被垃圾回收
largeObject = null
}
createGarbage()
但一个对象被多次引用时,例如闭包等场景,外部可以访问到内部变量时,该对象就是不会被垃圾回收的,依然存在,如下示例:
js
function createClosure() {
let largeArray = new Array(1000000).fill("Some data")
// 创建一个闭包,引用了 largeArray
return function () {
console.log(largeArray[0])
}
}
let closure = createClosure()
closure() // Some data