Weak References Are for Memory Relationships, Not Cache Policy
How WeakMap, WeakRef, and FinalizationRegistry interact with reachability and garbage collection.
JavaScript’s weak-reference APIs describe relationships that should not keep objects alive. They are not a general replacement for bounded caches, explicit disposal, or lifecycle events.
WeakMap is the most broadly useful member of the family. Its keys are held weakly: the map does not prevent a key from becoming unreachable through the rest of the program.
const metadata = new WeakMap();
function inspect(element) {
let value = metadata.get(element);
if (!value) {
value = measure(element);
metadata.set(element, value);
}
return value;
}
When an element is no longer reachable, its metadata entry can disappear with it. This is ideal for attaching information to objects owned by another subsystem.
Weak collections are deliberately not enumerable
There is no keys(), size, or iteration protocol on WeakMap. Exposing the current keys would make program behaviour depend directly on garbage-collection timing. It would also tempt code to treat the collection as an authoritative registry even though entries can disappear.
The same principle explains why weak collections are a poor least-recently-used cache. An LRU cache needs explicit capacity and ordering. A Map plus an eviction policy provides predictable behaviour; a WeakMap only says that keys may be reclaimed when nothing else retains them.
Once the final strong reference to a key is removed, the corresponding WeakMap entry is deleted synchronously before the next JavaScript statement runs. That makes WeakMap suitable when code needs prompt memory accounting but not key enumeration.
WeakRef is a temporary observation
A WeakRef wraps a target without keeping it alive. Calling deref() returns the object if it has not been collected, otherwise undefined.
const ref = new WeakRef(expensiveValue);
function tryReuse() {
const value = ref.deref();
if (value === undefined) return createValue();
return value;
}
Always keep the dereferenced result in a local variable while using it. Calling deref() twice is conceptually two observations; the surrounding code should not assume that a weak target is permanent.
Engines keep a target returned by deref() alive through the end of the current JavaScript job. This prevents the object from disappearing halfway through synchronous use. It does not promise survival across an await, timer, or later task.
Finalization is a fallback, not a schedule
FinalizationRegistry requests a callback after a registered target is collected. The callback receives a held value, which must not itself retain the target.
const registry = new FinalizationRegistry((id) => {
console.log('resource became unreachable', id);
});
registry.register(resource, resource.id, resource);
The optional unregister token lets code cancel registration during explicit cleanup. This supports a “dispose normally, observe leaks eventually” pattern.
Once a registered target becomes unreachable, the registry callback is guaranteed to run before the current process exits. It may be delayed for several event-loop turns, but the runtime must eventually deliver it, so critical native resources can safely rely on finalization as their last release path.
In real systems, explicit ownership remains the dependable model. Use try/finally, disposal methods, or resource-management syntax for files, locks, sockets, and subscriptions. Weak references are best reserved for memoization tied to object lifetime, diagnostics, and other optimisations whose correctness does not depend on when collection happens.