Screen_Shot_2014-05-30_at_1.37.33_PM

THE SALSIFY SMARTER ENGINEERING BLOG

Posted by Dan Freeman

Oct 17, 2013 1:15:00 PM

Superman_shield

For newcomers to JavaScript, the language's prototypical inheritance model is often a source of confusion, particularly for those used to classical inheritance in languages like Java, C++, and Ruby. Right off the bat, new JS developers generally hit a few blocking questions:

  • How do I set up inheritance without classes?
  • What exactly does new even do?
  • What on earth is going on with the this keyword?

Once those are settled, the pieces begin to fall into place, and they can actually get started writing software. Eventually, though, another question arises: what about super?

Invoking Methods from a Known Prototype

It turns out JavaScript's lack of super keyword is due to the fact that the language's other features make it unnecessary. Since "methods" on an object are really just fields that happen to contain functions, the prototype model gives us everything we need to call super methods in JavaScript. All an object with an overriding method has to do is grab the function with the same name from its prototype and apply it:

That gets the job done, but it's a bit verbose, and it can be even worse in certain situations. For instance, at Salsify we use composite views from Marionette to simplify rendering individual item views for every model in a collection. But what happens when we're extending another composite view and want to override one of its item view's methods?

In addition to the verbosity of this approach, it has the downside of needing to know the name of the prototype when you're writing the method. This means that changes to your inheritance hierarchy can require updating every call site for a prototype method. It also prohibits reusing function implementations that need to invoke overridden functionality.

Invoking Methods from the Constructor's Prototype

To solve the problem of needing to know the prototype name (and put a cap on the verbosity), it might be tempting to use the object's constructor attribute:

This works for the simple case; calling doSomething on an instance of Child behaves as expected. Unfortunately, calling it on an instance of Grandchild doesn't go so smoothly. Since constructor always refers to the actual function used to instantiate the object, the attempted super call instead becomes recursive, resulting in a stack overflow.

On the Shoulders of Giants

JavaScript has been around long enough (nearly two decades now) that plenty of different approaches have been taken to making it less tedious to invoke overridden methods. In 2008, John Resig published a solution on his blog that has seen considerable adoption. Lukas Olson has even packaged it as a robust Backbone plugin on GitHub. The end result of this approach is that it allows super invocations like this:

Short, sweet, and simple. The down side to this approach is that it ends up wrapping every function in a helper that maintains the value of the _super attribute, updating it before and after each invocation. This means the stack is littered with calls through this wrapper during debugging. It can also have performance implications, because every method invocation (regardless of whether it's a super call or not) has to set up a try/finally block, which can be expensive in some JS engines.

The Prototype.js library attacks the problem from the outside in. To invoke a super method in a Prototype class, you declare your method to take a special $super parameter as its first argument. The framework will pick this up and pass in the overridden implementation when the method is called.

Prototype's solution is consise and easy to read. It does, however, require some magic on the part of the framework to detect whether methods want the $super parameter or not. It also bears considering that the value of that parameter is not the actual super implementation; it's wrapped in a helper that handles passing that method its $super parameter if necessary, binding this for the method invocation, etc. Overall, the Prototype approach ends up being fairly complex to implement.

Outside the JavaScript Box

Of course, the other option if a language doesn't have the feature you want is to switch to another language. Both CoffeeScript and TypeScript have super built in, and compile it down to a static reference using the object's prototype.

CoffeeScript's super looks similar to many of the JS solutions we've looked at:

TypeScript's, on the other hand, more closely resembles Java:

Our Approach

Here at Salsify, though, we're still operating in JavaScript, and we're experimenting with another approach. It has roughly the same form factor as John Resig's solution, but without requiring the wrapper function around every method. Instead, we define a dynamic super property on the base prototype in our hierarchy that takes on the value of whatever method its caller overrides.

Using it is as simple as:

One key difference between this and the earlier solutions is that this.super returns the actual implementation defined on the parent, without any wrappers or bound state being pulled along. There's no performance overhead when you call it or extra frames to wade through in the debugger; it's just a vanilla JavaScript function.

There are two things you may notice about this implementation. First, the use of Object.defineProperty means it won't work in IE 8 or older. Second, it uses the dubious Function.caller, which will no longer officially be available in the next version of JavaScript, generally referred to as ES6 or "Harmony". Fortunately, ES6 will also introduce language support for accessing super methods, so the moment that becomes a problem will also be the moment we won't need this shim any longer.

Until then we'd love to hear your take on any of the approaches here, or others you've tried out.

Topics: Engineering

comments powered by Disqus