JavaScript WeakMap and WeakRef

Store object-keyed data and hold references without blocking garbage collection in JavaScript.

TL;DR

  1. 01Store object-keyed properties using weak references to support garbage collection.
  2. 02Hold object references without preventing the engine garbage collection process.
  3. 03Use FinalizationRegistry objects to configure cleanup callbacks for collected references.

Tips

  1. 01Attach metadata records to external object keys safely using self-cleaning WeakMap caches.
  2. 02Create memory-sensitive caches using WeakRef to let engines collect references under pressure.

Warnings

  1. 01Avoid setting primitive keys on WeakMap because keys must always be object references.
  2. 02Do not rely on FinalizationRegistry for critical updates since callback execution timing is unpredictable.

What WeakMap Is

    WeakMap key values

    Stores key-value entries where keys are object references.

    const cache = new WeakMap();
    const user = { id: 1 };
    cache.set(user, { clicks: 3 });
    Weak references

    Allows garbage collection of key objects when references disappear.

    let el = document.querySelector("#widget");
    cache.set(el, { clicks: 0 });
    el = null; // entry can be collected now
    Key constraints

    Requires object keys, throwing errors when primitives are passed.

    // cache.set("key", 1); // TypeError

Non-Enumerable design

    No iteration

    Prohibits access to size, keys, and values properties.

    const wm = new WeakMap();
    console.log(wm.size); // undefined
    // [...wm]; // TypeError: wm is not iterable
    Deterministic safety

    Hides garbage collector actions to keep behavior predictable.

    // Non-enumerable design prevents observation

Private Data Caches

    Property encapsulation

    Attaches private attributes to objects without changing structures.

    const privateData = new WeakMap();
    class Account {
      constructor(b) { privateData.set(this, { b }); }
      getBal() { return privateData.get(this).b; }
    }
    Metadata attachment

    Caches calculated values keyed to specific element instances.

    const sizeCache = new WeakMap();
    function getBounding(el) {
      if (!sizeCache.has(el)) {
        sizeCache.set(el, el.getBoundingClientRect());
      }
      return sizeCache.get(el);
    }

WeakRef

    Object wrappers

    Wraps object references without blocking garbage collection.

    let obj = { data: "large" };
    const ref = new WeakRef(obj);
    ref.deref(); // { data: "large" }
    deref() checks

    Queries deref references, verifying presence before accesses.

    obj = null;
    // after GC executes:
    const target = ref.deref();
    if (target) console.log(target.data);

FinalizationRegistry

    Cleanup callbacks

    Registers callbacks to execute after objects are collected.

    const reg = new FinalizationRegistry(held => {
      console.log("collected:", held);
    });
    reg.register(obj, "label");

In Practice

FAQ