Where’s my inheritance?

Update: from what I can gather from going through the source code, $super in Prototype actually refers to the method in the parent class, rather than the old method in the current class. My point about the other libraries mentioned below stands, though. Also, my apologies to Dan, whom I cornered at @media Ajax and quizzed about Prototype’s stance on this issue.

There is one design decision in JS.Class that sets it aside from all the other inheritance libraries I looked at (Prototype, Base and Inheritance). With all those libraries, super means “the previous version of this method in this class”, rather than “the current version of this method in the parent class”. Now, if you’ve just built a class by inheriting from a parent class and then overwriting some of its methods, those two definitions amount to the same thing.

But JavaScript is a dynamic language, and I can add new methods to any class whenever I want. What if I want to replace a non-super-calling method with a method that does call super. More often than not, I don’t care how the method used to work (if I do, I can easily store a reference to it myself) but I do care about how the parent class’ method works. The aforementioned libraries leave you in the lurch here.

Now, consider the following inheritance situation:

var Car = JS.Class({});
var Ford = JS.Class(Car);
var ModelT = JS.Class(Ford);

Car.method('drive', function() {
    return 'Driving my Car';
});

ModelT.method('drive', function() {
    return this._super() + ', a Model T';
});

var a = new ModelT().drive();

Ford.method('drive', function() {
    return 'Driving my Ford';
});

var b = new ModelT().drive();

Car defines a drive method, which is inherited by Ford and ModelT through the prototype chain. Then ModelT defines its own drive function, which uses super. Clearly, this should refer to the drive method inherited from Car. But then, Ford defines a drive method. This will not be inherited by ModelT – it now has its own drive method – but the question arises about what super within ModelT#drive should refer to.

The prevailing wisdom with the libraries mentioned above is that it ought to refer to the current class’ previous implementation of the method, rather than the parent class’ current implementation. That is, super refers to the overridden method at the time the new method is defined, rather than the parent’s method at the time the new method is called.

Personally, I think this is madness. If I’m trying to debug some JavaScript and see the word this._super, the very first thing I’m going to do is inspect this.klass.superclass at that point in the code. I’m sure as hell not going to start wading through a large codebase (and if you need an inheritance model, I’m assuming you have a large codebase) trying to find out the order in which a particular method was overridden. Both semantically and practically, I think the “current method in superclass” method is superior to the “previous method in current class” one. In the above example, a contains "Driving my Car, a Model T" while b contains "Driving my Ford, a Model T".

It seems that a fair number of people disagree with this policy though, including the Ruby language (which JS.Class is modelled on), which actually inspects included Modules for super methods to use before working its way up through superclasses. I’d be really interested to know why this is, and whether there are compelling reasons not to do things my way. I might add support for the other libraries’ way of working if anyone can persuade me…

If you’ve enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It’s full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.