WeakRef holds a target (an object or a non-registered symbol) weakly: the reference alone does not keep it alive. ref.deref() returns the target, or undefined if the engine has reclaimed it. A weak reference is never a guarantee, and each use needs the undefined check.
FinalizationRegistry lets you register a target with a held value, and a callback that receives the held value some time after the target is collected. register(target, held, token?); unregister(token) cancels it.
The spec is deliberately weak:
- It "does not make any guarantees that any object or symbol will be garbage collected." Unreachable objects "may be released after long periods of time, or never at all."
- Cleanup callbacks run as a separate job after synchronous code ends. Implementations "may omit FinalizationRegistry callbacks for any reason", for example if the application is shutting down.
- Pinning (KeepDuringJob):
new WeakRef(target)andderef()callAddToKeptObjects, which holds the target strongly untilClearKeptObjectsruns at the end of the current synchronous job. So the target cannot vanish between twoderef()calls in the same job; it can between jobs.
Consequences: collection timing differs by engine, flags, heap pressure and version, so code that "works" while testing may behave differently in production. Do not use these for resource management (use try/finally, using, AbortController), correctness, or logic that must run. Legitimate uses are narrow: a cache of recomputable values that may be dropped under memory pressure, and a last-resort cleanup of something external (a native handle) if the owner forgot to dispose of it.