Using catch-alls to implement super in SpiderMonkey 11

Another thing that always been bothering me about OOP in JS is that calling super is such a pain. Some libraries do ugly hacks like checking the source (by calling toString on their constructor and method function objects) and then wrap the original function in another function that encapsulates a variable called super. Other libraries do even worse things like rewriting the code of the function and eval’ing it. These hacks work in most cases (not in Opera for mobile phones) but it is very inefficient.

Yesterday morning (when reading a lengthy email to es-discuss) I realized that I could probably use catch-alls to emulate super. Catch-alls is a SpiderMonkey feature that allows an object to have a method that will be called when a non existing method is called. Catch-alls are proposed for ES-Harmony. The idea I had was to create a super object that uses the catch-all mechanism to call the method on the super class with this bound to the current object. I wanted to achieve something like this:

function B() {
  supr(1, 2, 3);
  this.method = function(x) {
    supr.method(x);
    ...
  };
}
inherits(B, A);

Instead of having to write:

function B() {
  A.call(this, 1, 2, 3);
  this.method = function(x) {
    A.prototype.method.call(this, x);
    ...
  };
}
inherits(B, A);

So supr needs to be a function which has the __noSuchMethod__ method set to call the method of the [[Prototype]] object.

Take one:

function B() {
  var self = this;
  var supr = function() {
    return A.apply(self, arguments);
  };
  supr.__noSuchMehod__ = function(name, args) {
    return A.prototype[name].apply(self, args);
  };

  supr(1, 2, 3);
  this.method = function(x) {
    supr.method(x);
    ...
  };
}
inherits(B, A);

Lets take a look at the inherits function. It is something all js dev heads should have in their repertoire. It is extremely useful and it allows you to declare inheritance without having to create an instance of the super class.

function inherits(child, parent) {
  var f = function() {};
  f.prototype = parent.prototype;
  child.prototype = new f;
  child.prototype.constructor = child;
  child.superClass = parent;
}

The last two lines are important because JS does not maintain the mapping to the constructor and by adding the superClass property it allows us to easily get access to the parent constructor.

With the innards of the inherits function known combined with take one above we can write a helper function that makes creating the supr object a lot easier.

function createSuper(self, opt_constr) {
  var constr = opt_constr || arguments.callee.caller;
  function supr() {
    return constr.superClass.apply(self, arguments);
  };
  supr.__noSuchMethod__ = function(name, args) {
    if (typeof constr.superClass.prototype[name] == 'function') {
      return constr.superClass.prototype[name].apply(self, args);
    }
    throw Error('No such method, ' + name);
  };
  return supr;
}

And then we can use this.

function A(x) {
  print('In A constructor, x = ' + x);
}
A.prototype.a = function(x) {
  print('In A.prototype.a, x = ' + x);
};


function B(x) {
  print('In B constructor');
  var supr = createSuper(this);

  supr(x || 'From B');
  var className = 'B';
  this.a = function(x) {
    print('In ' + className + '.a');
    supr.a(1);
  };
}
inherits(B, A);


function C(x) {
  print('In C constructor');
  var supr = createSuper(this);

  supr(x || 'From C');
}
inherits(C, B);
C.prototype.a = function() {
  var supr = createSuper(this, C);
  print('In ' + 'C' + '.a');
  supr.a('supr.a called from a c object');
};


print('Creating an instance of B');
b = new B;
b.a(2);

print('');
print('Creating an instance of C');
c = new C;
c.a();

Try this (Firefox only)

This works because supr is recreated for every constructor and is bound lexically Don’t do this.supr = supr and think you can reuse it. It will not work. This does not require that you put the methods that needs super inside the constructor but if you don’t you will have to call createSuper and pass in your constructor from your method which is not very efficient and it is hardly much better than using the straight forward way to call super by just getting the function of the super class prototype and call it by name (A.prototype.a.call(this, 1)).

Conclusion

Once again I find that adding code to make things simpler in JS is unsuccessful. The costs for adding better ways to do things don’t pay for itself. (Except for the inherits function of course.)

  • http://technomedia.co.uk/blog/ Dan

    Wow – just like PHP’s magic methods :) I’ve starting to realise that trying to emulate classical inheritence just isn’t worth it in a lot of cases – I think a mixin implementation (that applys scope to the original method rather than simply copying methods to other objects) works better in a lot of cases.

  • Grey

    Wait – so all this effort is just to be able to write

    supr.method(…);

    instead of

    supr.prototype.method(…);

    (assuming supr == superClass in the latter case)?

    Hmm, I had not heard of __noSuchMethod__ before.. it’s interesting :) Though Gecko-only… I don’t suppose there’s something like __noSuchProperty__ (at least it doesn’t appear here: https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Object )?

  • http://erik.eae.net Erik Arvidsson

    Dan: Can you elaborate what you mean with mixin here? Most time when I see people do mixin in js it is just copying methods from one object onto another object.

    Grey: No, all this work was to be able to write

    B.prototype.method = function(…) {

    supr.method(…);
    };

    instead of:

    B.prototype.method = function(…) {
    B.superClass.method.call(this, …);
    };

    I still thought it was a fun exercise :-)

  • Malte

    Hey, you might also be interested in the way Joose implements the override method modifier (affectively achieving the affect that you describe):
    http://code.google.com/p/joose-js/wiki/BuildingAClass#override

    The relevant code is in the default meta class for methods:
    http://code.google.com/p/joose-js/source/browse/trunk/lib/Joose/Method.js#46

    I like Joose’s approach to JS OOP much better because you actually declare that do want to achieve something instead of building/using the bare bones of the language every single time.

  • Grey

    @Erik: Uh, yeah, should’ve seen that, my bad. And yes, I do think exercises like these are a lot of fun, even when they’re fruitless (well, not completely, just not really practical :P). Yours even motivated me to pick up a project I had abandoned a few months ago (doesn’t have anything to do with inheritance, though); thanks for that.

  • http://technomedia.co.uk/blog Dan

    @Eric: To clarify, Instead of just copying methods into your mixed object, I was recently playing around with something like this:

    MyApp.MyMixableModule = {
    mixin : function(subject){
    for (var p in this) {
    if (this.hasOwnProperty(p) && p !== ‘mixin’ && subject[p] === undefined) {
    subject[p] = (function(sub, func){
    return function(){
    func.apply(sub, arguments);
    };
    })(subject, this[p]);
    }
    }
    }
    }

    This is not polished or tested yet, but I have plans to continue playing around in this space and writing a blog post about it.

    Any thoughts?

  • http://erik.eae.net Erik Arvidsson

    Dan: This is the classic mixin pattern as used by dojo and others and it does copy the function. Your solution seems to automatically bind the methods to the instance as well.

  • Pingback: Ajaxian » Implementing super in JavaScript()

  • http://none Attila

    Dear Eric,

    I have been banging my head to understand the inherit method since a long time (it is what ExtJS uses, too). I tried to ask others to shed a light for me but so far couldn’t find any decent explanation on what happens there and why. Would you mind explaining it in a post or in email for me?

    thank you,
    Attila

  • Pingback: Implementing super in JavaScript | Slightly Nervous()

  • Pingback: Chasing super()