# 弱引用映射 - WeakMap

WeakMap (opens new window)是ES2015新增的数据类型,它的功能是提供了一个键值对的集合。

WeakMap的使用场景极少,但在某些场景下使用WeakMap解决问题会更简单方便。

# WeakMap特征

  1. 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
    
  2. 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,可以看见弱引用使用完毕即被清除
    
  3. 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之前不存在弱引用。

最近更新时间: 2020/5/26 19:03:58