垃圾回收(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的出现,主要是为了避免容易忽视的内存泄漏。
相关链接:
Understanding Weak Reference In JavaScript