Emulate super in javascript

Raynos picture Raynos · Nov 7, 2011 · Viewed 20.9k times · Source

Basically is there a good elegant mechanism to emulate super with syntax that is as simple as one of the following

  • this.$super.prop()
  • this.$super.prop.apply(this, arguments);

Criteria to uphold are :

  1. this.$super must be a reference to the prototype. i.e. if I change the super prototype at run-time this change will be reflected. This basically means it the parent has a new property then this should be shown at run-time on all children through super just like a hard coded reference to the parent would reflect changes
  2. this.$super.f.apply(this, arguments); must work for recursive calls. For any chained set of inheritance where multiple super calls are made as you go up the inheritance chain, you must not hit the recursive problem.
  3. You must not hardcode references to super objects in your children. I.e. Base.prototype.f.apply(this, arguments); defeats the point.
  4. You must not use a X to JavaScript compiler or JavaScript preprocessor.
  5. Must be ES5 compliant

The naive implementation would be something like this.

var injectSuper = function (parent, child) {
  child.prototype.$super = parent.prototype;
};

But this breaks condition 2.

The most elegant mechanism I've seen to date is IvoWetzel's eval hack, which is pretty much a JavaScript preprocessor and thus fails criteria 4.

Answer

hugomg picture hugomg · Nov 7, 2011

I don't think there is a "free" way out of the "recursive super" problem you mention.

We can't mess with the this because doing so would either force us to change prototypes in a nonstandard way, or move us up the proto chain, losing instance variables. Therefore the "current class" and "super class" must be known when we do the super-ing, without passing that responsibility to this or one of its properties.

There are many some things we could try doing but all I can think have some undesireable consequences:

  • Add super info to the functions at creation time, access it using arguments.calee or similar evilness.
  • Add extra info when calling the super method

    $super(CurrentClass).method.call(this, 1,2,3)
    

    This forces us to duplicate the current class name (so we can look up its superclass in some super dictionary) but at least it isn't as bad as having to duplicate the superclass name, (since coupling against the inheritance relationships if worse then the inner coupling with a class' own name)

    //Normal Javascript needs the superclass name
    SuperClass.prototype.method.call(this, 1,2,3);
    

    While this is far from ideal, there is at least some historical precedent from 2.x Python. (They "fixed" super for 3.0 so it doesn't require arguments anymore, but I am not sure how much magic that involved and how portable it would be to JS)


Edit: Working fiddle

var superPairs = [];
// An association list of baseClass -> parentClass

var injectSuper = function (parent, child) {
    superPairs.push({
        parent: parent,
        child: child
    });
};

function $super(baseClass, obj){
    for(var i=0; i < superPairs.length; i++){
        var p = superPairs[i];
        if(p.child === baseClass){
            return p.parent;
        }
    }
}