Technology · JavaScript

Advanced

JavaScript WeakMap and WeakRef

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

TL;DR
  1. 01WeakMap keys must be objects and stay garbage-collectable.
  2. 02Weak references never prevent the garbage collector from reclaiming memory.
  3. 03Use WeakRef and FinalizationRegistry only for specialized memory cases.

What WeakMap Is

  • A WeakMap stores key-value pairs where keys must be objects and are held with weak references.
    const cache = new WeakMap();
    const user = { id: 1 };
    cache.set(user, { loginCount: 3 });
    cache.get(user); // { loginCount: 3 }
    
  • A weak reference doesn't stop the garbage collector from reclaiming the key's memory.
  • Once no other code references the key object, the entire entry becomes eligible for collection.
    let el = document.querySelector('#widget');
    cache.set(el, { clicks: 0 });
    el = null; // the WeakMap entry can now be collected too
    
  • WeakMap supports get, set, has, and delete, the same core methods as Map.
  • Primitive values like strings and numbers cannot be used as WeakMap keys.
  • WeakSet works the same way but stores objects directly instead of key-value pairs.

Why Entries Aren't Enumerable

  • WeakMap deliberately has no size, keys(), values(), or iteration support.
    const wm = new WeakMap();
    wm.size; // undefined
    [...wm]; // TypeError: wm is not iterable
    
  • Exposing the entry list would let code detect exactly when garbage collection happened.
  • Engines need freedom to collect memory at unpredictable times without breaking observable behavior.
  • This tradeoff means WeakMap only works well when you already know which key to look up.
  • If you need to list, count, or iterate entries, use a regular Map instead.
  • The non-enumerable design is what makes the automatic cleanup safe and consistent.

Private Data and Caches

  • Attach private data to object instances without adding a visible property to the object itself.
    const privateData = new WeakMap();
    class Account {
      constructor(balance) {
        privateData.set(this, { balance });
      }
      getBalance() {
        return privateData.get(this).balance;
      }
    }
    
  • This pattern predates native private class fields (#field) and still works on plain objects.
  • Build a cache keyed by object identity, like memoized results tied to a specific DOM element.
    const sizeCache = new WeakMap();
    function getSize(el) {
      if (!sizeCache.has(el)) {
        sizeCache.set(el, el.getBoundingClientRect());
      }
      return sizeCache.get(el);
    }
    
  • The cache entry disappears automatically when the DOM element is removed and garbage collected.
  • This avoids manual cleanup code that a regular Map-based cache would otherwise require.
  • Use a WeakSet to track which objects have already been processed, without retaining them.
    const processed = new WeakSet();
    function process(obj) {
      if (processed.has(obj)) return;
      processed.add(obj);
    }
    

WeakRef

  • WeakRef wraps a single object so you can hold it without preventing garbage collection.
    let obj = { data: 'large payload' };
    const ref = new WeakRef(obj);
    ref.deref(); // { data: 'large payload' }
    
  • Call .deref() to access the wrapped object, which returns undefined once collected.
    obj = null;
    // later, after garbage collection runs:
    ref.deref(); // may now be undefined
    
  • WeakRef is useful for caches that should release memory under pressure rather than grow forever.
  • Always check the result of .deref() before using it, since the object may already be gone.
  • Avoid WeakRef for typical application logic — it exists for specialized memory-sensitive tools.
  • Combine WeakRef with a regular Map of keys to references for a self-cleaning cache pattern.

FinalizationRegistry and Choosing the Right Tool

  • FinalizationRegistry lets you register a callback that may run after an object is garbage collected.
    const registry = new FinalizationRegistry((heldValue) => {
      console.log('cleaned up:', heldValue);
    });
    registry.register(obj, 'obj-label');
    
  • The callback timing is never guaranteed, so don't use it for anything time-critical or required.
  • It's meant for releasing external resources tied to an object, like closing a related handle.
  • Choose WeakMap when you need object-keyed data that should clean itself up automatically.
  • Choose a regular Map when you need iteration, primitive keys, or guaranteed entry lifetime.
  • Reserve WeakRef and FinalizationRegistry for advanced caching or resource-tracking libraries, not everyday app code.
Tips
  1. 01Reach for a WeakMap when you need to attach metadata to objects you don't own, like DOM nodes.
  2. 02Prefer a regular Map whenever you need to iterate entries, check size, or use primitive keys like strings or numbers.
Warnings
  1. 01WeakMap and WeakSet throw a TypeError if you try to use a primitive string or number as a key.
  2. 02FinalizationRegistry callbacks run at an unpredictable time, so never rely on them for critical cleanup logic like closing files.
  3. 03Holding a strong reference to a WeakMap key elsewhere in your code keeps the entry alive and defeats its purpose.
FAQ
JavaScript Type Coercion