Please support this book: buy it or donate

31. WeakMaps (WeakMap)



WeakMaps are similar to Maps, with the following differences:

  • They can be used to attach data to objects, without preventing those objects from being garbage-collected.
  • They are black boxes where a value can only be accessed if you have both the WeakMap and the key.
    The next two sections examine in more detail what that means.

31.1. Attaching values to objects via WeakMaps

This is how you attach a value to an object via a WeakMap:

  1. const wm = new WeakMap();
  2. {
  3. const obj = {};
  4. wm.set(obj, 'attachedValue'); // (A)
  5. }
  6. // (B)

In line A, we attach a value to obj. In line B, obj can be garbage-collected. The notable feature of WeakMaps is that wm does not prevent obj from being garbage-collected. This technique of attaching a value to an object is equivalent to storing a property of that object externally. If wm were a property, the previous code would look as follows.

  1. {
  2. const obj = {};
  3. obj.wm = 'attachedValue';
  4. }

31.1.1. The keys of a WeakMap are weakly held

The keys of a WeakMap are said to be weakly held: Normally, references to an object prevent the object from being garbage-collected. However, WeakMap keys don’t. Additionally, WeakMap entries, whose keys were garbage-collected, are also (eventually) garbage-collected.

Weakly held keys only make sense for objects. Thus, you can only use objects as keys:

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

31.2. WeakMaps as black boxes

It is impossible to inspect what’s inside a WeakMap:

  • For example, you can’t iterate or loop over keys, values or entries. And you can’t compute the size.
  • Additionally, you can’t clear a WeakMap, either – you have to create a fresh instance.
    These restrictions enable a security property. Quoting Mark Miller: “The mapping from weakmap/key pair value can only be observed or affected by someone who has both the weakmap and the key. With clear(), someone with only the WeakMap would’ve been able to affect the WeakMap-and-key-to-value mapping.”

31.3. Examples

31.3.1. Caching computed results via WeakMaps

With WeakMaps, you can associate previously computed results with objects, without having to worry about memory management. The following function countOwnKeys() is an example: it caches previous results in the WeakMap cache.

  1. const cache = new WeakMap();
  2. function countOwnKeys(obj) {
  3. if (cache.has(obj)) {
  4. return [cache.get(obj), 'cached'];
  5. } else {
  6. const count = Object.keys(obj).length;
  7. cache.set(obj, count);
  8. return [count, 'computed'];
  9. }
  10. }

If we use this function with an object obj, you can see that the result is only computed for the first invocation, while a cached value is used for the second invocation:

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

31.3.2. Keeping private data via WeakMaps

In the following code, the WeakMaps _counter and _action are used to store the data of virtual properties of instances of Countdown:

  1. let _counter = new WeakMap();
  2. let _action = new WeakMap();
  3. class Countdown {
  4. constructor(counter, action) {
  5. _counter.set(this, counter);
  6. _action.set(this, action);
  7. }
  8. dec() {
  9. let counter = _counter.get(this);
  10. if (counter < 1) return;
  11. counter--;
  12. _counter.set(this, counter);
  13. if (counter === 0) {
  14. _action.get(this)();
  15. }
  16. }
  17. }
  18. // The two pseudo-properties are truly private:
  19. assert.deepEqual(
  20. Reflect.ownKeys(new Countdown()),
  21. []);

31.4. WeakMap API

The constructor and the four methods of WeakMap work the same as their Map equivalents:

  • 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]