# 弱引用映射 - WeakMap
WeakMap (opens new window)是ES2015新增的数据类型,它的功能是提供了一个键值对的集合。
WeakMap的使用场景极少,但在某些场景下使用WeakMap解决问题会更简单方便。
# WeakMap特征
WeakMap键名只能为「对象」。而普通的键值对集合,例如Map等,其键名只能为「字符串」。
const map = new WeakMap(); const obj = {}; map.set(obj, 'value'); // WeakMap {{…} => "value"} map.set('key', 'value'); // Invalid value used as weak map key
WeakMap键名所引用的对象为弱类型。
// JS,目前除了WeakMap和WeakSet能够实现弱类型的对象,其余对象皆为强引用。 // 举个例子 const obj = [1, 2, 3, 4, 5]; // 创建一个常量obj,obj强引用数组[1, 2, 3, 4, 5] const key = [obj, 6]; // 此时创建一个常量key,key也强引用了数组[1, 2, 3, 4, 5] obj = null; // obj = null时,[1, 2, 3, 4, 5]并没有被垃圾回收机制回收 key = null; // 当key = null时,数组已经没有被任何对象强引用,此时数组才会被垃圾回收机制回收 // V8引擎(Node也使用的V8)的垃圾回收机制其中一条就是: // 对象被强引用个数为0时,才会被回收。
通过Node来验证一下:强引用回收的情况
node --expose-gc // 进入Node环境 // 在Node环境下输入以下代码 const bytesToMbs = bytes => (bytes / 8 / 1024 / 1024).toFixed(2) + 'MB'; global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 0.71MB,初始内存大小 const map = new Map(); // 创建Map let key = new Array(5 * 1024 * 1024); // 创建一个比较大的数组,方便看内存变化 map.set(key, 1); global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 5.65MB,数组此时被map和key同时强引用 map.delete(key); // 删除map对数组的强引用 global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 5.65MB,删除了map的强引用,但是内存并没有减少 key = null; // 删除了key对数组的强引用 global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 0.62MB,此时数组没有被任何变量强引用,所以回收
再通过Node来验证一下:弱引用(WeakMap)的情况
node --expose-gc // 进入Node环境 // 在Node环境下输入以下代码 const bytesToMbs = bytes => (bytes / 8 / 1024 / 1024).toFixed(2) + 'MB'; global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 0.71MB,初始内存大小 const map = new WeakMap(); // 创建WeakMap let key = new Array(5 * 1024 * 1024); // 创建一个比较大的数组,方便看内存变化 map.set(key, 1); global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 5.65MB,此时数组被key强引用,map弱引用 key = null; // 删除key对数组的强引用 global.gc(); bytesToMbs(process.memoryUsage().heapUsed); // 0.66MB,可以看见弱引用使用完毕即被清除
WeakMap不可枚举,无法执行遍历操作。
// 因为WeakMap不可枚举,所以只有在当前能够访问对象引用的前提下,才能访问WeakMap // 例如 // a.js const obj = {}; const map = new WeakMap(); map.set(obj, 'value'); // b.js // 这里没有obj,所以无法访问。 map.get(obj); // 这里必须得先能够访问到obj,才能访问到WeakMap
因此,根据这个特点——『能够访问对象引用时,才能访问该对象的WeakMap』,我们可以实现私有属性等功能。
# 使用场景
# 数据缓存/扩展属性
当需要在不修改原有对象的情况下,储存修改某些属性或者根据对象储存一些计算的值等,而又不想管理这些数据的死活时非常适合考虑使用WeakMap。
const person = new Person();
const notebook = new WeakMap();
notebook.set(person, {otherProperty: '其它属性'}); // 扩展其它属性
// notebook.set(person, getWeightStature(person)); // 根据原有属性获取身高体重比
# 私有属性
const privateData = new WeakMap();
export default class Person {
constructor(name, age) {
// this指向Person类
privateData.set(this, {name, age}); // 因此只能在Person类中访问privateData
}
getName() {
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
# 总结
MDN
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
注意
WeakMap无法在ES5中模拟,因为ES6之前不存在弱引用。