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 include
d Module
s 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…