ponyfoo.com

JavaScript Proposal for weak references: WeakRef

A proposal to implement weak references in JavaScript – which would expose weak references made possible by WeakMap and WeakSet is currently sitting at stage 0.

Improve this article
Nicolás Bevacqua
| 6 minute read | 0

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 WeakMap and WeakSet.

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.

Background

In a simple garbage-collected language, memory is a graph of references between objects, anchored in the roots such as global data structures and the program stack. The garbage collector may reclaim any object that is not reachable through that graph from the roots. Some patterns of computation that are important to JavaScript community are difficult to implement in this simple model because their straightforward implementation causes no-longer used objects to still be reachable. This results in storage leaks or requires difficult manual cycle breaking. Examples include:

  • 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.

Intended Audience

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.

Diagram without using WeakRef
Diagram without using WeakRef

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. If 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:

  • target is the object we wish to create a weak reference to
  • executor is an optional argument that can be used as a finalization callback
  • holdings is an optional argument provided to executor when it’s invoked for target

We could create a new WeakRef using makeWeakRef, as shown below.

var target = document.querySelector('.ponyfoo')
var weakRef = makeWeakRef(target)

The 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.

Diagram when using WeakRef
Diagram when using WeakRef

While there’s at least one non-weak reference to the DOM element matching .ponyfoo, 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 target, the 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 null.

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 null.

Use Cases

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.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments