29. WeakMaps

WeakMaps 与 Maps 类似,但有以下区别:

1. 它们可用于将数据附加到对象,而不会阻止这些对象被垃圾回收。
2. 它们是黑盒子,只有拥有 WeakMap 和密钥才能访问值。

接下来的两节将更详细地研究这意味着什么。

29.1. 通过 WeakMaps 将值附加到对象

这是通过 WeakMap 将值附加到对象的方法:

const wm = new WeakMap();
{
  const obj = {};
  wm.set(obj, 'attachedValue'); // (A)
}
// (B)

在 A 行中,我们将值附加到obj。在 B 行中,obj可以被垃圾收集。 WeakMaps 的显着特征是wm不会阻止obj被垃圾收集。将值附加到对象的这种技术等同于在外部存储该对象的属性。如果wm是属性,则前面的代码如下所示。

{
  const obj = {};
  obj.wm = 'attachedValue';
}

29.1.1. WeakMap 的键是弱的

据说 WeakMap 的键是 _ 弱保持 _:通常,对对象的引用会阻止对象被垃圾收集。但是,WeakMap 键没有。此外,WeakMap 条目(其密钥是垃圾收集的)也(最终)被垃圾收集。弱键只对对象有意义。因此,您只能将对象用作键:

> const wm = new WeakMap();
> wm.set(123, 'test')
TypeError: Invalid value used as weak map key

29.2. WeakMaps 为黑盒子

检查 WeakMap 中的内容是不可能的:

  • 例如,您不能迭代或循环键,值或条目。而你无法计算大小。
  • 此外,您无法清除 WeakMap - 您必须创建一个新的实例。

这些限制启用了安全属性。引用 Mark Miller :“弱映射/密钥对值的映射只能由同时具有弱映射和密钥的人来观察或影响。使用clear(),只有 WeakMap 的人才能够影响 WeakMap 和键值映射。“

29.3. 例子

29.3.1. 通过 WeakMaps 缓存计算结果

使用 WeakMaps,您可以将先前计算的结果与对象相关联,而无需担心内存管理。以下函数countOwnKeys()是一个示例:它将以前的结果缓存在 WeakMap cache中。

const cache = new WeakMap();
function countOwnKeys(obj) {
  if (cache.has(obj)) {
    return [cache.get(obj), 'cached'];
  } else {
    const count = Object.keys(obj).length;
    cache.set(obj, count);
    return [count, 'computed'];
  }
}

如果我们将此函数与对象obj一起使用,您可以看到结果仅针对第一次调用计算,而缓存值则用于第二次调用:

> const obj = { foo: 1, bar: 2};
> countOwnKeys(obj)
[2, 'computed']
> countOwnKeys(obj)
[2, 'cached']

29.3.2. 通过 WeakMaps 保存私人数据

在以下代码中,WeakMaps _counter_action用于存储Countdown实例的虚拟属性数据:

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}
// The two pseudo-properties are truly private:
assert.deepEqual(
  Reflect.ownKeys(new Countdown()),
  []);

练习:私人数据的 WeakMaps exercises/maps-sets/weakmaps_private_data_test.js

29.4. WeakMap API

WeakMap的构造函数和四种方法与的Map等价物的作用相同:

  • new WeakMap<K, V>(entries?: Iterable<[K, V]>) ^[ES6]^
  • .delete(key: K) : boolean ^[ES6]^
  • .get(key: K) : V ^[ES6]^
  • .has(key: K) : boolean ^[ES6]^
  • .set(key: K, value: V) : this ^[ES6]^
下一节:在 ES6 之前,JavaScript 没有集合的数据结构。相反,使用了两种解决方法:

1. 对象的键作为字符串集。
2. 数组作为任意值的集合(例如,通过.includes()检查元素是否在集合中),缺点是元素检查缓慢。

ECMAScript 6 具有数据结构Set,适用于任意值且具有快速执行元素检查。