Bastardizing the Javascript Inheritance Model to Provide this.super()

Background

I first came up with this idea about 6 months ago while I was working deep in the internals of AFrameJS. Javascript Inheritance is actually pretty awesome, it's prototypal inheritance model is supremely flexible as well as a memory efficient. Unfortunately for me, and probably many others that come from a Classical Inheritance background, we keep thinking in terms of Classes. Javascript dogmatists are always up in arms about this, saying we should use Javascript in the way Javascript was meant to be used - meh. This isn't about dogma, this isn't about "correctness" or anything of the sorts, this is about seeing what can be done.

So, generally, I always have a class declaration mechanism that allows me to define "Classes" and "Subclasses." A bit of classical style. Under the hood, these are really functions and prototypes. My original implementation is loosely based on Douglas Crockford's inheritance patterns, loosely based on Object.create with some additional sugar.

Class Declaration

The basic "Class" declaration system I use is almost always a variation of:

function Class( superclass, subclass ) {
    if( !subclass ) {
        subclass = superclass;
        superclass = null;
    }

    // F is our new class constructor, if specified on the subclass, use it,
    // otherwise create a new constructor that does nothing.
    var F = subclass.hasOwnProperty( 'constructor' ) ? 
        subclass.constructor : 
        function() {};

    if( superclass ) {
        // If there was a superclass, set our new classes prototype to it.
        F.prototype = new superclass();
        for( var key in subclass ) {
            // copy over subclass properties to new prototype.
            F.prototype[ key ] = subclass[ key ];
        }

        // A very very important bit of housekeeping here, keep track of
        // the superclass.  This is used later.
        F.superclass = superclass;
    }
    else {
        // no superclass, just set the prototype to be the subclass object.
        F.prototype = subclass;
    }

    // very important to reset the constructor as any class with a superclass
    // will have it overwritten
    F.prototype.constructor = F;

    return F;
}

This can be used in one of two ways, to create base classes, or to create sub classes.

A base class is as simple as:

var SuperClass = Class( {
    // objects and properties here
} );

A Subclass is then created using:

var SubClass = Class( SuperClass, {
   // objects and properties here.
} );

Declaring a constructor:

var ClassWithConstructor = Class( {
     constructor: function() {
         // do some amazing construction
     }
} );

Overriding Class Prototype Methods

This is all well and good, we can easily call an SuperClass' overridden method using the ClassName.superclass shortcut - it always points to the current Class' prototype's prototype object. This allows us to [relatively] easily call the overridden method:

var SubClass = Class( SuperClass, {
    ...
    overridden: function() {
        // Do some work here.

        return SubClass.superclass.overridden.call( this );
    }
} );

Without the sugar of having SubClass.superclass, what is normally found is something along the lines of:

var SubClass = Class( SuperClass, {
    ...
    overridden: function() {
        // Do some work here.

        return SuperClass.prototype.overridden.call( this );
    }
} );

Generally, I prefer the first, the main reason being if I ever decide to change SubClass' SuperClass, I don't want to have to update in a million places.

The Problem

I got to thinking, even

       ...
        return SubClass.superclass.overridden.call( this );
       ...

is too much typing. It's too verbose, not concise, and doesn't really get to the intention of what we are really trying to do. 99.9% of the time, we want to call the exact same named function, but on the superclass.

The Proposed Solution

       ...
        return this.super();
       ...

Wouldn't that be a million times easier to read?

This is an experiment on making that happen.

The Implementation

I have thought of several potential other ways of doing this, this is my second pass - the first was a memory hogging monster. In the beginning I thought, "well, instead of a call to 'new', I generally use some sort of create function, so why not have the create function wrap all outer level functions with a decorator, the decorator is a housekeeping function that keeps track of the current location in the prototype chain as well as the function name. Once we have the housekeeping set up, we can call this.super and the super function will know where in the prototype chain it currently is, and can find the next function to call." A long, rambling though, sort of like this post.

Dan Newcome read the original post and suggested that things could be much simpler - his idea is now how things are implemented, this second version much more memory efficient than the first. The original version used a lot of memory because it wrapped every function of every created object with dynamically created decorators. This meant an explosion of functions would be in memory if many objects were created. Not ideal. Dan's idea was simple "Why not go one more layer out?" Which translated in my head to "Create a wrapper class instead of individual wrapper functions." So, that is now the approach. Instead of creating individual wrappers around each function of each object, we create wrappers around each function for each class. If a class is re-used several times, only one copy of the decorators are ever created.

Still, unfortunately, every object must be created using Class.create - a function that is very similar to Object.create. Class.create takes care of all of the housekeeping that we unfortunately do. It decorates the entire class being created with a new class. The new class has an identical interface to the original class, except every initial call to a class function is wrapped in a housekeeping function. This housekeeping function keeps track of the current function name as well as the current position in the prototype chain. Any time this.super is called, the housekeeper searches for the next function of the same name in the prototype chain, and calls it.

The Code - Class.create

This uses both Function.prototype.bind and Function.prototype.curry. If you haven't used these two functions, I highly recommend them, they can really cut down on cruft code.

// The creation function.  Called like: Class.create( constructor_to_use );
Class.create = function( constr ) {

    // This takes care of when the user calls "this.super()"
    // The initial call to the decorator and each call to this.super
    // sets up currLevel to point to the function that should
    // be run next.
    function _super( key, level ) {
        var info = findNext( key, level ),
            retval;

        if( info.next ) {
            // overwrite this.super with a reference ourselves, but set
            //    the level to be one above this in the prototype chain.
            this.super = _super.bind( this, key, info.level );

            retval = info.next.apply( this, [].slice.call( arguments, 2 ) );

            // get rid of this.super so this is not callable from the  
            // outside.
            this.super = null;
            delete this.super;
        }

        return retval;
    }

    function findNext( key, level ) {
        var next;
        do {
            // cycle until we find the next prototype level that has
            //    the function that we are trying to call.
            next = level && level.prototype.hasOwnProperty( key
                    ) && level.prototype[ key ];
            level = level.superclass;
        } while( level && !next );
        return { next: next, level: level };
    }

    if( !constr.wrapper ) {
        var overridden = {};
        for( var key in constr.prototype ) {
            // we are creating an identical interface of functions that do
            // our housekeeping.  We are using Function.prototype.curry, which
            // comes from prototypejs[prototypejs.org]
            if( typeof constr.prototype[ key ] === 'function' && key !== 'constructor' ) {
                overridden[ key ] = _super.curry( key, constr );
            }
        }

        // save off the wrapper for the next creation of this class.
        constr.wrapper = Class( constr, overridden );

    }

    var obj = new constr.wrapper();
    obj.constructor = constr;

    return obj;
}


A bit of a beast, but it works. You can call this.super from any function without having to specify which function to call. You can even call this.super with parameters.

Examples of use

this.super can be called with no parameters.

    ...
    overridden: function() {
        return this.super();
    },
    ...

It can also be called with parameters.

    ...
    overridden: function() {
        return this.super( arg1, arg2, ... );
    },
    ...

The full code, including unit tests can be found on GitHub.

If you have ideas on how to make this better/more powerful, please let me know!