强引用与弱引用
先说说引用是什么,一般来说强引用和弱引用针对的都是引用类型数据,而不是基本类型数据。引用类型数据就好比对象和数组,当然还有后面的集合(Set)和Map等等,它们都是需要在堆内存中新开辟一片空间进行存储的。比如,我们在定义一个对象的时候,如下面代码,这时候就存在引用了,变量obj对这个对象的引用:
let obj = {
name:"aaa"
}
而强引用和弱引用有什么区别呢?又会带来怎样的影响?其实最主要的影响是对JS引擎的垃圾回收机制(GC)有关,当然这里我们说的是GC算法中的标记清除法。拿对象举例,当一个对象有强引用的关系存在时,GC就不会将其回收掉,如果一个对象没有任何引用或者是只存在弱引用,那么这个对象就会被GC给回收掉。
简而言之,强引用和弱引用的区别就在于,GC回收是不会管对象有没有弱引用存在的,只有对象存在强引用,才不会被回收掉。而对于弱引用是怎么来的需要等下我们讲到WeakSet和WeakMap的时候会遇到。
FinalizationRegistry
为了方便测试,我们介绍一个类:FinalizationRegistry。通过FinalizationRegistry创建出来的对象允许在上面注册一个对象,这个对象在被销毁的时候就会调用在new的时候传入的回调函数。注意,GC算法回收是不定时的,可能它几秒后就会打印,也可能是几十秒,所以使用该方法需要在控制台多等一下。
const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象被销毁", value);
});
let obj = { name: "aaa" };
finalRegistry.register(obj, "obj");
obj = null;
Set、WeakSet、Map、WeakMap的使用
Set
和数组类似,但区别在于Set内保存的元素不能重复,每个元素都是唯一的,创建时可以传入一个数组来创建。
Set常见的属性:
- size:返回Set中元素的个数。
Set常用的方法:
- add(value):添加某个元素,返回Set对象本身;
- delete(value):从set中删除和这个值相等的元素,返回boolean类型;
- has(value):判断set中是否存在某个元素,返回boolean类型;
- clear():清空set中所有的元素,没有返回值;
- forEach(callback, [, thisArg]):通过forEach遍历set;
另外,Set支持for of遍历
const arrSet = new Set([10,11])
arrSet.add(10)
arrSet.add(20)
arrSet.forEach(item => {
console.log(item)
})
for (const item of arrSet) {
console.log(item)
}
// delete
arrSet.delete(10)
console.log(arrSet)//Set(2) { 11, 20 }
// has
console.log(arrSet.has(11))// true
// clear
arrSet.clear()
console.log(arrSet)//Set(0) {}
WeakSet
WeakSet与Set基本一样,常用的属性和方法也和Set一致,但和Set的区别有两个:
- WeakSet中只能存放对象类型,不能存放基本数据类型
- WeakSet对元素的引用是弱引用
而上面我们也解释过,弱引用不会对GC回收对象产生影响,无论对象是否含有弱引用,只要它没有强引用就会被GC回收。下面我们结合FinalizationRegistry来测试一下Set和WeakSet的区别。
const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象被销毁", value);
});
let obj = { name: "aaa" };
let obj2 = { name: "bbb" };
finalRegistry.register(obj, "obj");
finalRegistry.register(obj2, "obj2");
const set1 = new Set([obj]);
const set2 = new WeakSet([obj2]);
obj = null;
obj2 = null;
上面代码中我们分别创建了Set和WeakSet并引用了obj和obj2,最后我们将这两个对象都赋值为null,也就是取消掉全局对这两个对象的强引用。等待一段时间后,我们发现我们定义的obj2被销毁了,但obj还存在,这就说明obj还存在引用,并且还是强引用,而这个引用来自set1对obj的引用。这也印证了WeakSet对对象是一个弱引用,上面代码虽然WeakSet引用了obj2,但obj2最后还是被销毁了。
Map
Map用于存储映射关系,它其实和对象有点像,对象也是用于存储映射关系的。但是在对象中,只能用字符串和Symbol作为属性名,而在某些情况下,我们可能需要将一个对象或者函数作为属性名,这时候就需要用到Map了。
Map常见的属性:
- size:返回Map中元素的个数;
Map常见的方法:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
- clear():清空所有的元素;
- forEach(callback, [, thisArg]):通过forEach遍历Map;
Map也可以通过for of进行遍历。
const obj = { name: "aaa" };
const map = new Map([[obj, "aaa"]]);
// set
map.set("bbb", "111");
console.log(map);//Map(2) {{…} => 'aaa', 'bbb' => '111'}
//get
console.log(map.get(obj));//aaa
// has(key)
console.log(map.has(obj));//true
// delete(key)
map.delete(obj);
console.log(map);//Map(1) {'bbb' => '111'}
// clear
map.clear();
console.log(map);//Map(0) {size: 0}
const map2 = new Map([
["aaa", "111"],
["bbb", "222"],
]);
map2.forEach((item, key) => {
console.log(item, key);
});
for (const item of map2) {
console.log(item[0], item[1]);
}
for (const [key, value] of map2) {
console.log(key, value);
}
WeakMap
WeakMap和Map基本一样,区别如下:
- WeakMap的key只能使用对象,不接受其他的类型作为key
- WeakMap的key对象的引用是弱引用
验证方法可参照WeakSet进行验证,对于WeakMap的应用,可在实现对象响应式中体现。详细可在我接下来准备写的vue2、vue3响应式原理这篇文章查看。
结尾
对于使用Set还是WeakSet,Map还是WeakMap,都取决于我们需不需要回收对象。因为含有强引用的对象是不能被GC回收的,为了避免内存泄漏,我们才会选择在合适情况下使用WeakSet和WeakMap来对一个对象进行弱引用,后续就算这个对象设置为null了,那么也不会存在被WeakSet和WeakMap的引用所束缚而不被GC回收,这样能很好的节省我们的内存。