Time for another ECMAScript proposal draft. This time we’ll discuss
WeakRef. Weak references had their first – indirect – appearance in ES6, with the arrival of
The Background and Intended Audience sections, found below, contain good indicators of why the proposal was put forward, as well as the intended audience. They were extracted from the proposal’s repository. Afterwards we’ll move onto use cases and its API.
- MVC and data binding frameworks
- Reactive-style libraries and languages that compile to JS
- Caches that require cleanup after keys are no longer referenced
- Proxies/stubs for comm or object persistence frameworks
- Objects implemented using external or manually-managed resources
Two related enhancements to the language runtime will enable programmers to implement these patterns of computation: weak references and finalization.
A strong reference is a reference that causes an object to be retained; in the simple model all references are strong. A weak reference is a reference that allows access to an object that has not yet been garbage collected, but does not prevent that object from being garbage collected. Finalization is the execution of code to clean up after an object that has become unreachable to program execution.
For example, MVC frameworks often use an observer pattern: the view points at the model, and also registers as an observer of the model. If the view is no longer referenced from the view hierarchy, it should be reclaimable. However in the observer pattern, the model points at its observers, so the model retains the view (even though the view is no longer displayed). By having the model point at its observers using a weak reference, the view can just be garbage collected normally with no complicated reference management code.
Similarly, a graphics widget might have a reference to a primitive external bitmap resource that requires manual cleanup (e.g., it must be returned to a buffer pool when done). With finalization, code can cleanup the bitmap resource when the graphics widget is reclaimed, avoiding the need for pervasive manual disposal discipline across the widget library.
The garbage collection challenges addressed here largely arise in the implementation of libraries and frameworks. The features proposed here are advanced features (e.g., like proxies) that are primarily intended for use by library and framework creators, not their clients.
Thus, the priority is enabling library implementors to correctly, efficiently, and securely manage object lifetimes and finalization.
The API for weak references
When using strongly-held references, cutting off one of them doesn’t affect other references.
var a, b; a = b = document.querySelector('.ponyfoo') a = undefined // ... a GC pause later ... // b still references the DOM element
In the diagram below – also retrieved from the proposal’s repository – the target object is the DOM element itself, while the Client and Service Object are references to it. Here, the case is being made for the use case where we have a core “Client” component (such as the DOM element) and one or many “Service Object” components that are only necessary as long as the DOM element is being referenced.
In the situation we’ve just described, we’d have to dereference
b whenever we dereference
a. In particular, we’d have to keep track of reference counting ourselves, so that we know when the DOM element is no longer being referenced by a relevant party, and then we can dereference the element in _“secondary” – that is, “Service” – references to it.
Under this proposal, there’s a
makeWeakRef global function and a
WeakRef built-in class. Using
makeWeakRef returns a
WeakRef object that’s pointed at
target is no longer referenced, then the
WeakRef object will stop pointing at it after a full garbage collector sweep.
makeWeakRef(target, executor?, holdings?)
The parameters to
makeWeakRef are as follows:
targetis the object we wish to create a weak reference to
executoris an optional argument that can be used as a finalization callback
holdingsis an optional argument provided to
executorwhen it’s invoked for
We could create a new
makeWeakRef, as shown below.
var target = document.querySelector('.ponyfoo') var weakRef = makeWeakRef(target)
WeakRef object has two methods. There’s
get(), which retrieves the weakly held
target object reference or
null if the object has been garbage collected away. Then there’s
.clear() which nulls out the underlying weak reference while preventing the
executor from being invoked, without the need for a full GC sweep.
The following diagram shows how
WeakRef can act as an intermediary that deals with reference counting of strongly held references and retrieval of a weakly held reference on our behalf.
While there’s at least one non-weak reference to the DOM element matching
weakRef.get() target will return the element. In our case, this means we’ll be able to retrieve the weakly held reference to the DOM element for as long as the
target variable is pointing at it.
weakRef.get() === target
If we de-referenced
WeakRef interface can no longer guarantee that it’ll return the DOM element. After a full GC sweep and finalization,
weakRef.get will start to return
target = undefined // de-reference target // after a full GC, weakRef no longer points at target weakRef.get() === null
Instead of waiting for a GC sweep you could alternatively call
weakRef.clear to break the weakly held reference. In that case,
weakRef.get would immediately begin to return
Whenever you previously wanted to use a
WeakMap just so you could map a DOM element to an object that provided extra features related to it, you could now use
WeakRef. Similarly, in every case where you want to provide extra features around an object you don’t have control over, a
WeakRef is a great way to do so without holding onto it with a strongly-held reference that prevents it from being garbage collected. Something that, under certain circumstances, could end up in a hard-to-detect memory leak situation.