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.)