As always, MDN's docs help.
A WeakRef object contains a weak reference to an object, which is called its target or referent. A weak reference to an object is a reference that does not prevent the object from being reclaimed by the garbage collector. In contrast, a normal (or strong) reference keeps an object in memory. When an object no longer has any strong references to it, the JavaScript engine's garbage collector may destroy the object and reclaim its memory. If that happens, you can't get the object from a weak reference anymore.
In almost every other part of JS, if some object (A) holds a reference to another object (B), B will not be garbage-collected until A can be fully garbage-collected as well. For example:
// top level
const theA = {};
(() => {
// private scope
const theB = { foo: 'foo' };
theA.obj = obj;
})();
In this situation, the theB
will never be garbage collected (unless theA.obj
gets reassigned) because theA
on the top level contains a property that holds a reference to theB
; it's a strong reference, which prevents garbage collection.
A WeakRef, on the other hand, provides a wrapper with access to an object while not preventing garbage collection of that object. Calling deref()
on the WeakRef will return you the object if it hasn't been garbage collected yet. If it has been GC'd, .deref()
will return undefined
.
FinalizationRegistry deals with a similar issue:
A FinalizationRegistry object lets you request a callback when an object is garbage-collected.
You first define the registry with the callback you want to run, and then you call .register
on the registry with the object you want to observe. This will let you know exactly when something gets garbage collected. For example, the following will log Just got GCd!
once the obj
gets reclaimed:
console.log('script starting...');
const r = new FinalizationRegistry(() => {
console.log('Just got GCd!');
});
(() => {
// private closure
const obj = {};
r.register(obj);
})();
You can also pass a value when calling .register
that gets passed to the callback when the object gets collected.
new FinalizationRegistry((val) => {
console.log(val);
});
r.register(obj, 'the object named "obj"')
will log the object named "obj"
it gets GC'd.
All this said, there is rarely a need for these tools. As MDN says:
Correct use of FinalizationRegistry takes careful thought, and it's best avoided if possible. It's also important to avoid relying on any specific behaviors not guaranteed by the specification. When, how, and whether garbage collection occurs is down to the implementation of any given JavaScript engine. Any behavior you observe in one engine may be different in another engine, in another version of the same engine, or even in a slightly different situation with the same version of the same engine. Garbage collection is a hard problem that JavaScript engine implementers are constantly refining and improving their solutions to.
Best to let the engine itself deal with garbage collection automatically whenever possible, unless you have a really good reason to care about it yourself.