持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
垃圾回收(Garbage Collection)机制中的可达性(Reachability)
不同于C或C++,Javascript作为一种高级编程语言,在创建对象时会自动分配内存,而当对象不再被使用时会自动清除内存(C或C++由开发者主动去调取相应的 API 来完成空间管理,而Js中没有提供相应API)。对象不再被使用而被释放内存的过程被成为垃圾回收。
谈到垃圾回收机制,就必须要理解垃圾回收机制中的可达性(Reachability)概念。
可达性:可以通过引用、作用域链等方式访问到的对象就是可达对象
举个例子:
let obj = { name: "xm" }; // 创建了一个对象,这个对象的引用次数为1
let ali = obj; // 对象的引用次数变为2
obj = null; // 对象的引用次数变为1,依然是可达对象
再举个例子:
function objGroup(obj1, obj2) {
obj1.next = obj2;
obj2.prev = obj1;
return {
o1: obj1,
o2: obj2,
};
}
let obj = objGroup({ name: "obj1 name" }, { name: "obj2 name" });
delete obj.o1 // 取消了 obj 中 o1 的引用
delete obj.o2.prev // 取消了 obj2 中对于 o1 的引用,此时就无法找到 obj1 这个对象空间,obj1 就会被认为是垃圾,被回收
强引用
继续用上个例子:
let obj = { name: "xm" }; // 创建了一个对象,这个对象的引用次数为1
let ali = obj; // 对象的引用次数变为2
obj = null; // 对象的引用次数变为1,依然是可达对象
console.log(ali) // 依然可以打印出 { name: 'xm' }
发现当obj被置为null了,但打印ali的时候还是能打印出对象内容。这是因为ali与对象之间存在强引用,强引用能够阻止对象被垃圾回收。
弱引用
通常情况下,Javascript中对对象的引用是强引用,要在Javascript中实现弱引用,怎么办呢?别担心,ES6中已经引入了WeakSet 与 WeakMap,弱引用不再是问题。
let human = new WeakMap();
let man = { name: "Joe Doe" };
human.set(man, "done")
console.log(human.get(man)) // 输出done
man = null
console.log(human.get(man)) // 输出undefined
上述代码,当man参数被设置为null时,内存中对原始对象的唯一引用(weakMap对其的引用)是弱引用,弱引用不会阻止垃圾回收。
WeakSet与WeakMap 的应用领域
缓存(Caching)
举个例子:我们创建一个cachedResult.js的文件,内容如下:
let cachedResult = new Map();
function keep(obj) {
if (!cachedResult.has(obj)) {
let result = obj;
cachedResult.set(obj, result);
}
return cachedResult.get(obj);
}
console.log(cachedResult.size); // 打印出 0, Map(0) {}
let obj = { name: "Frank" };
let resultSaved = keep(obj)
obj = null;
console.log(cachedResult.size); // 打印出 1, Map(1) { { name: 'Frank' } => { name: 'Frank' } }
假设使用Map,在我们不需要obj对象时,需要去手动清理cachedResult对象。而当我们换成WeakMap,我们不需要去主动清理,也就是说,一旦对象被垃圾收集,缓存的结果将自动从内存中删除:
let cachedResult = new WeakMap();
// A function that stores a result.
function keep(obj) {
if (!cachedResult.has(obj)) {
let result = obj;
cachedResult.set(obj, result);
}
return cachedResult.get(obj);
}
let obj = { name: "Frank" };
let resultSaved = keep(obj)
console.log(cachedResult.get(obj)); // 打印出 { name: 'Frank' }
obj = null;
console.log(cachedResult.get(obj)); // 打印出 undefined
额外的数据存储
假设我们正在构建一个电子商务(e-commerce)平台,其中有一个统计访客的程序,那么在访客离开时,统计的数量visitorCount需要相应减小。这个功能如果使用Map的话,就很容易出现bug(在客户离开时需要对visitorCount进行内存释放,否则会在内存中无限增长,占用内存),但如果是使用WeakMap,就不需要主动对visitorCount进行释放(一旦人(对象)无法访问,就会被自动被垃圾回收)。
let visitorCount = new WeakMap();
function countCustomer(customer){
let count = visitorCount.get(customer) || 0;
visitorCount.set(customer, count + 1);
}
let person = {name: "Frank"};
countCustomer(person) // 访客访问时
person = null; // 访客离开时
总结
WeakSet、WeakMap的出现,主要是为了避免容易忽视的内存泄漏。