ECMAScript 6: automatically binding extracted methods

This blog post demonstrates how to use ECMAScript 6 proxies to automatically bind methods that are extracted from an object.

The problem with extracting methods

It’s a common JavaScript gotcha: If you extract a method from an object, it becomes a normal function. It loses the connection to the object and acessing properties via this doesn’t work, any more. Let’s look at an example:

Binding automatically

So far, we have only used ECMAScript 5, now we are entering ECMAScript 6 territory. We’ll make use of several ECMAScript 6 features. We initially won’t use classes [1], to demonstrate how the basics work. The next section explains how to use auto-binding with classes.

Thanks to a recent decision by TC39, ECMAScript 6 proxies allow us to fix the problem. We control access to methods via a proxy: If a method is called, the proxy is notified via invoke() and ignores the notification (meaning that the method call will progress normally). If, however, the method’s property is read, the proxy intercepts and binds the method. The following function wraps a proxy around an object.

The wrapped object returned by autoBind may not be the first member in a chain of prototype objects. Then it only receives a get notification if the property hasn’t been found in a prior object. Thus, the result (*) must come from target. In contrast, this must be bound to receiver (**), so that the bound method has access to properties of prior objects.

To reduce memory consumption, it is best to wrap the proxy around the instance prototype [2] of a constructor.
For example:

Caching bound methods

Currently, each read access to a method creates a new bound method. We can help with registering event listeners and similar use cases by caching the returned results. We need to cache those results per receiver, because the same wrapped object may be the prototype of multiple instances.

In ECMAScript 6, we can customize instance allocation via the class method @@create[3]. Its name is a public symbol that we need to import from a standard module (whose actual name has yet to be determined).
Now we don’t change the instance prototypes, any more. Instead, we allocate instances whose prototypes are auto-binding versions of the instance prototypes. As a result, subclassing works without the weird extends Person.origProto. And instanceof now works for indirect instances, but not for direct instances. Thankfully, we can fix that via the @@hasInstance method.

Conclusion

The approach for auto-binding presented here is only a proof of concept. Performance issues (hidden classes etc.) will probably make it unsuitable in practice. Caching, however, is not a performance issue, because methods will rarely be accessed via “get”.

As an aside, Python always auto-binds methods. Maybe JavaScript can provide built-in support for it in the future.