WeakMap 的基本 API & 使用场景

162 阅读2分钟

ES6 新增的弱映射 (WeakMap) 是一种新的集合类型,WeakMap 是 Map 的 “兄弟” 类型,其 API 也是 Map 的子集

WeakMap 基本 API

实例化弱映射

有参和无参构造

// 初始化一个空的 弱映射关系
const wm = new WeakMap();

// 弱映射只能以对象为键
const objKey1 = { id: 1 };
const objKey2 = { id: 2 };
const objKey3 = { id: 3 };

// 初始化填充的弱映射
const wm2 = new WeakMap([
  [objKey1, "any value1"],
  [objKey2, "any value2"],
  [objKey3, "any value3"],
]);

// 获取值
log(wm2.get(objKey1)); // log: any value1

初始化是一个事物操作

如果因为在初始化的时候出现无效的键,则会导致整体初始化失败

// 初始化是一个 事务操作,如果其中一个键无效,会导致整个初始化失败
const wm3 = new WeakMap([
  [objKey1, "any value1"],
  ["invalidKey", "anyVal2"], // 因为无效的键导致初始化失败 ❌
]);

// 如上代码将出错 TypeError: Invalid value used as weak map key 

WeakMap「弱」的含义

弱引用的意思是不属于正式的引用,当键不存在被其他引用的时候不会影响垃圾回收,当键被垃圾收回之后,这个键值对就会从弱映射中消失

const wm4 = new WeakMap();
wm4.set({}, "val");

这个 demo 中 set 方法初始化一个新对象并将它作为一个字符串的键,因为没有指向这个对象的其他引用,

所以这行代码执行完毕后,这个对象键就会被当作垃圾回收,因此这个对象键和其对应的值也会消失在这个弱映射中,这个值也没有被引用(基本类型),所以在键值对就小时候,值也会成为垃圾回收的目标

const wm = new WeakMap();
const container = {
  key: {},
};

wm.set(container.key, "val");

function removeReference() {
  container.key = null;
}

log(wm.get(container.key)); // log: val
//  container 维护者一个对弱映射的引用,因此这个对象键不会成为垃圾回收的目标。
// 调用了 removeReference 就会摧毁键对象的最后一个引用,垃圾回收程序就会把这个 键值对 清理掉
removeReference();
log(wm.get(container.key)); // log: undefined

WeakMap 的应用

1. 模拟私有变量

const createUserById = (id) => {
  // 利用闭包将 vm 放在特定的作用域,防止外部获取后可以自由获取值
  const wm = new WeakMap();
  class User {
    constructor(id) {
      this.idProperty = Symbol("id");
      this.setId(id);
    }

    setPrivate(property, value) {
      // this 代表的就是当前的实例对象
      const privateMembers = wm.get(this) || {};

      privateMembers[property] = value;
      // wm 以 当前实例作为 key, privateMembers 作为值进行储存
      wm.set(this, privateMembers);
    }

    getPrivate(property) {
      return wm.get(this)[property];
    }

    setId(id) {
      this.setPrivate(this.idProperty, id);
    }

    getId() {
      return this.getPrivate(this.idProperty);
    }
  }

  return new User(id);
};

const user = createUserById("id_230611");
log(user.getId()); // id_230611
user.setId(456);
log(user.getId()); // 456

2. DOM 节点元数据

WeakMap 实例不会妨碍垃圾回收,非常适合保存关联数据,WeakMap 不会影响 垃圾回收的 操作

<body>
  <button id="login">login button</button>
  <script>
    const m = new Map();
    const loginBtn = document.querySelector("#login");

    // 给 loginBtn 关联一些数据
    m.set(loginBtn, { disabled: true });

    /**
       * 在执行上述的代码之后,页面被 js 改变了,
       * 登陆按钮 从 DOM 树中删除了,带式犹豫映射中还保存着按钮的引用,
       * 所以对应的 DOM 节点仍然会被保留在内存中,除非明确将其从映射中删除或者等到映射本身被销毁
       * 为了解决上述问题,还得看 弱映射,如果是弱映射,当节点从 DOM 书中删除后,垃圾回收程序就可以立即释放其内存(如果其他地方没有应用这个 loginBtn 节点)
       * */
    const wm = new WeakMap();
    wm.set(loginBtn, { disabled: true });
  </script>
</body>