Technology · JavaScript
AdvancedJavaScript WeakMap and WeakRef
Store object-keyed data and hold references without blocking garbage collection in JavaScript.
TL;DR
- 01WeakMap keys must be objects and stay garbage-collectable.
- 02Weak references never prevent the garbage collector from reclaiming memory.
- 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, anddelete, 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
WeakRefwraps 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 returnsundefinedonce 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
FinalizationRegistrylets 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
- 01Reach for a WeakMap when you need to attach metadata to objects you don't own, like DOM nodes.
- 02Prefer a regular Map whenever you need to iterate entries, check size, or use primitive keys like strings or numbers.
Warnings
- 01WeakMap and WeakSet throw a TypeError if you try to use a primitive string or number as a key.
- 02FinalizationRegistry callbacks run at an unpredictable time, so never rely on them for critical cleanup logic like closing files.
- 03Holding a strong reference to a WeakMap key elsewhere in your code keeps the entry alive and defeats its purpose.
FAQ
- A Map holds strong references to its keys, so entries stay in memory until you delete them explicitly. A WeakMap holds weak references, so an entry disappears automatically once nothing else references the key object. WeakMap also restricts keys to objects and isn't iterable, unlike Map.
- Garbage collection timing isn't predictable, so the spec deliberately omits iteration, size, and keys() from WeakMap and WeakSet. Exposing the entry list would let code observe when garbage collection happened, which engines need to keep hidden. If you need to enumerate entries, use a regular Map instead.
- WeakRef wraps an object so you can hold a reference to it without preventing garbage collection. Call .deref() to get the object back, which returns undefined if it has already been collected. It's a low-level tool meant for caches and specialized memory-sensitive code, not everyday use.
- Use private class fields (#field) for state that belongs to instances of a class you control. Use a WeakMap when you need to attach private data to objects you don't control, like instances from another library. WeakMap also works for plain objects, which don't support private fields at all.
- No, the specification explicitly allows engines to never call it, for example if the program exits first. Treat any cleanup callback as a bonus optimization, never as required logic. Use try/finally or explicit dispose methods for anything that must run reliably.