前言
js垃圾回收机制有好几种,不过这里就只记录一下引用计数和标记清除。
垃圾的定义/内存泄漏
有人大概总结是,只要是开发人员不期望使用,但又长期保留的存储内容,就是垃圾。
大白话就是,这个存储内容我现在用完了,且用完后就不需要了,但是我没主动清掉或js引擎没给我清掉,还一直存在着,就是垃圾。
产生了垃圾也被称为内存泄漏的一种,垃圾如果不断的堆积,内存不断累加,造成内存溢出。
我认为内存泄漏还有一种意思是不断的循环执行某个动作,内存不断累加,导致内存溢出;
闭包所带来的存储常驻不是内存泄漏,他是我们预期的行为,其存储内容也不符合垃圾的定义。
引用计数
let a = { a: 1 }
b = a // 浅拷贝,a此时被引用了一次
那么只要b后面一直被使用到,a就永远不会销毁,除非咱们把b销毁了
b = null // a不再被引用,自动清除
现在浏览器已经不用这种方法了,而且引用计数会出现循环引用的bug。
循环引用
这个循环引用的介绍是在慕课网课上看到的,说是两个之间相互引用,就永远没法清除。不知道理解的对不对。
const a = {}
const b = {}
a.c = b
b.c = a
常量a和b在下面被相互引用,所以不会被清除,且就算后面a、b用不到了,也不会被清除。
在IE老版本里,这种循环引用在dom对象里就会造成内存泄漏。
let div1 = document.getElementById('div1')
div1.a = div1
div1.Bigdata = {} // 附上一个大的数据
标记清除
说是js会不间断的从window开始,挨个遍历节点,看还能不能找到代码里写的某个值或者变量,找不到就删除对应的内存。
例如:
const data = {}
function fn() {
const obj = { x: 100 }
data.obj = obj
}
fn()
首先执行fn,执行完后,js从window开始遍历,先遍历出data这个全局变量,接着遍历data,发现有个obj属性,这个obj属性是obj常量给予的,且也是个浅拷贝,所以这个常量不能被销毁。
总的来说就是,看还能不能访问到目录,不能的话就在将来某个时间点浏览器自动清除这块内存。
所以和引用计数完全不一样,例如同样的引用计数里的例子:
let a = { a: 1 }
b = a
b = null // b的原先内容再也访问不到了,那块栈空间记录被清除,但a还能访问到,所以a没被清除
如何检查内存情况
用控制台-性能
注意框起来的就可以了,右边的垃圾桶每次我们启动性能记录的时候都要点一次清除。蓝色坡线就是内存增加曲线最右边有记录最小值和最大值。
WeakMap和WeakSet
先看这个例子:
const map = new Map()
function fn() {
const obj = { a: 1 }
map.set('a', obj)
}
fn()
根据标记清除和浅拷贝的原理,obj是不会被清除的。在这种场景下我们其实是希望obj能自动被清除的,咋办。
这时候可以改用WeakMap:
const wMap = new WeakMap()
function fn() {
const obj = { a: 1 }
wMap.set(obj, 'WeakMap的key必须是引用类型')
}
fn()
此时,obj即使是引用类型,在WeakMap的机制下去赋值,也变成了深拷贝,obj会被标记清除掉。
这样的特性还带来了一个好处,因为引用类型的赋值变成了深拷贝,所以这个引用类型就算被人为赋值null,也不会影响该变量的值。
同理WeakSet也是一样的,具体这两个东西的使用方式我就不记录了。这两个数据类型在现在的库和框架中因为要考虑性能和深拷贝特性,所以被经常使用。