Gorm Casper10 August 2015

Calling the prototype with this._super()

Here’s a nifty little trick that I recently added to my personal bag of dirty js tools. I’m not terribly fond of trying to emulate classes in JavaScript, but its usefulness is best exemplified within that context.

Let’s say we have a “class” system in JavaScript, where extending a class means the superClass gets set as the prototype of the subClass. Much like how backbone does it. But let’s say we would like to have a function, this._super(), in each of the methods of the subClass that calls the exact method that it itself is overwriting.

So basically, we want to replace this:

var subClass = SuperClass.extend({
    foo: function (arg1, arg2) {
        SuperClass.prototype.foo(arg1, arg2);
        // ...
    }
});

with this:

var subClass = SuperClass.extend({
    foo: function (arg1, arg2) {
        this._super();
        // ...
    }
});

Notice the lack of arguments in the super call. It looks a bit like magic if you ask me.

Well, it is actually fairly simple but takes a bit of work. You have to run through each method of the sub class (you can conveniently add this to the extend function), and whenever you encounter one that has an equivalent on the super class, then you wrap it with…

function addSuper (subFn, superFn) {
    return function () {
        var self = this,
            args = arguments;

        this._super = function () {
            return superFn.apply(self, args);
        };

        return subFn.apply(self, args);
    }
}

That’s it. That is how simple it is. Here is an example of how to use it with the above subClass.foo:

subClass.foo = addSuper(subClass.foo, SuperClass.foo)

The subClass.foo now has a this._super that it can freely call. Here’s an example that is easier to test (just copy/paste to the console):

function supr () {
    console.log('supr was called with', arguments);
}

function inc (x) {
    this._super();
    return x + 1;
}

inc = addSuper(inc, supr);

inc(2);
// console: 'supr was called with [2]'
// → 3

Notice that calling inc before it is wrapped by addSuper will throw an error because this._super hasn’t yet been defined.

I intentionally kept the code as simple as possible, but there is a lot more you can do with it. Add it to extend like I mentioned, copy over the name so your stack traces are easier to understand, allow the sub class to pass its own arguments, etc. Anyway, here is a gist of the code above.