Calling the "base" with prototypal inheritance in JavaScript

I wrote a little bit about how inheritance was designed to work in JavaScript sometime back. Briefly, inheritance in JavaScript is "prototypal" in nature and instead of types inheriting from other types, we have object instances inheriting from other object instances. Being a weakly typed language, the traditional interpretation of inheritance as it applies to a static strongly typed language does not apply here. Each object in JavaScript has a hidden reference to another object called its "prototype". Member references (i.e. when you access a property like so: obj.prop) are resolved by the runtime by first looking the object itself up for the member and if not found then looking for it in the object's prototype. If the member is still not found then the prototype's prototype is inspected and so forth till the prototype of the root Object object is encountered at which point the member is declared as non-existent. The blog post I linked to earlier talks about this in detail.

I am working on a project where I have an inheritance hierarchy such as the following:

var Base = Object.create({}, {
    init: {
        value: function() {
            //
            // default initialization here
            //
            print("Base.init");
        }
    }
});

var Derived = Object.create(Base, {
    //
    // override init
    //
    init: {
        value: function() {
            //
            // call base here
            //
            print("Derived.init");
            Object.getPrototypeOf(this).init.call(this);
        }
    }
});

Derived.init();

This produces the following output:

Derived.init
Base.init

This makes sense because I am calling init on the Derived instance and calling Object.getPrototypeOf on Derived should return a reference to Base which we are subsequently able to use to invoke its (i.e. Base's) version of init. Wanting to make the syntax a tad more natural I decided to extend Object.prototype to include an "uber" getter property that will return a reference to the "base" object from which the current object inherits. Here's what I came up with:

Object.defineProperty(Object.prototype, "uber", {
    get: function () {
        return Object.getPrototypeOf(this);
    },
    enumerable: false,
    configurable: false
});

This would now allow me to call the base like so:

var Derived = Object.create(Base, {
    //
    // override init
    //
    init: {
        value: function() {
            //
            // call base here
            //
            print("Derived.init");
            this.uber.init.call(this);
        }
    }
});

Derived.init();

Great. Now consider what happens in the following snippet:

var o1 = Object.create(Derived);
o1.init();

For all intents and purposes this perhaps looks like it should work (or it did so to me at least!). Turns out, this doesn't quite work the way we might expect it to. Let's step through what the JavaScript engine actually does when it encounters the call to init via o1.

  1. Does o1 have an "own" (see Object.hasOwnProperty) property called init? Nope, it does not.
  2. Does o1``s prototype have an "own" property calledinit?o1`s prototype isDerivedand it does indeed have a property calledinit. Great, invokeDerived.initusingo1as the object context (Derived.init.call(o1)`).
  3. The problem arises when the JavaScript engine encounters line 2 of Derived.init. Remember that "this" here is a reference to o1 and calling Object.getPrototypeOf on it is simply going to result in a reference to Derived being returned! So in effect, we are making a recursive call to Derived.init!

Taking it to its logical conclusion it is easy to see that this code will result in Derived.init being called over and over again till the JS engine runs out of stack space.

Adding a teeny bit of type metadata

If you think about the problem a bit you learn that the fundamental issue has to do with being able to somehow map member functions with the specific object where it is defined in such a way that this mapping is programmatically accessible at runtime. For example, we need to be able to associate the object Derived with its implementation of the method init. And this association must somehow be made accessible from Object.uber so that it can return the correct prototype object reference. One rather straightforward way that we could perhaps achieve this is by simply passing Derivedto Object.uber. Like so:

Object.prototype.uber = function (obj) {
    return Object.getPrototypeOf(obj);
}

var Base = Object.create({}, {
    init: {
        value: function () {
            //
            // default initialization here
            //
            print("Base.init");
        }
    }
});

var Derived = Object.create(Base, {
    //
    // override init
    //
    init: {
        value: function () {
            //
            // call base here
            //
            print("Derived.init");
            this.uber(Derived).init.call(this);
        }
    }
});

var o1 = Object.create(Derived);
o1.init()

But I really wanted to be able to just say this.uber instead of having to pass the reference to the object where the method in question has been defined. This however will be possible only if we extend Object to include some additional support routines. First we add a function called inherit that is defined like so:

Object.inherit = function (proto, props) {
    var o = Object.create(proto, props);

    Object.getOwnPropertyNames(o).forEach(function (pn) {
        if (typeof (o[pn]) === "function")
            o[pn]["___type___"] = o;
    });

    return o;
}

Object.inherit simply delegates to Object.create and performs one small activity in addition. It enumerates all the member methods in the newly created object and tacks on a property called ___type___ to them. This property will simply point back to the newly created object. This bit of metadata will then be used from uber to retrieve a reference to the object where the method in question was created. With this new bit of metadata handy we modify uber to look like this:

Object.defineProperty(Object.prototype, "uber", {
    get: function () {
        var o = arguments.callee.caller["___type___"];
        if (!o) {
            throw "Uh oh! No metadata!";
        }
        return Object.getPrototypeOf(o);
    },
    enumerable: false,
    configurable: false
});

arguments.callee refers to the current function, i.e., the function where the current line is executing. This is particularly useful in situations such as the above where the function in question is an anonymous function and there really isn't any other way to refer to it. Function objects have a magic property called caller that become available as they are executing. Here's a small sample that illustrates this:

function bar() {
    print(bar.caller);
}

function foo() {
    bar();
}

foo();
print(bar.caller === null);

Here's the output we get on running this snippet:

function foo() { bar(); }
true

As you can tell, outside of the call context bar.callerevaluates to null but while it is executing, it contains a reference to foo since bar was invoked from foo. We use this aspect of JS to access the function that invokes uberwhich in turn should contain a reference to the object where the function was defined via ___type___. Once we get that object it's a simple matter of returning its prototype. With all this support infrastructure in place, code like the following should work just fine:

var Base = Object.inherit({}, {
    init: {
        value: function () {
            //
            // default initialization here
            //
            print("Base.init");
        }
    }
});

var Derived = Object.inherit(Base, {
    //
    // override init
    //
    init: {
        value: function () {
            //
            // call base here
            //
            print("Derived.init");
            this.uber.init.call(this);
        }
    }
});

var MoreDerived = Object.inherit(Derived, {
    init: {
        value: function () {
            print("MoreDerived.init");
            this.uber.init.call(this);
        }
    }
});

//
// create instances that inherit from MoreDerived
//
var o1 = Object.create(MoreDerived);
var o2 = Object.create(o1);
o2.init();

Here's the output this produces:

MoreDerived.init
Derived.init
Base.init

OK, but what happens if somebody tacks on a new member method or overrides an existing one after Object.inherit? Will this.uber work from that method? Clearly, it won't because the hidden ___type___ member is added to the member functions only from Object.``inherit. This means that we're going to have to add another little utility to Object that wraps this little functionality. Here goes:

Object.override = function (o, name, fn) {
    Object.defineProperty(o, name, {
        value: fn
    });
    fn["___type___"] = o;
}

Now, we can add new member methods to objects using this function. Here's an example:

var o1 = Object.create(MoreDerived);
Object.override(o1, "init", function () {
    print("o1.init");
    this.uber.init.call(this);
});

var o2 = Object.create(o1);
Object.override(o2, "init", function () {
    print("o2.init");
    this.uber.init.call(this);
});

o2.init();

And here's the output we get:

o2.init
o1.init
MoreDerived.init
Derived.init
Base.init

That's it. As long as we use Object.inherit and Object.override to create our derived instances and to override methods respectively, we can confidently use this.uber to refer to the correct parent prototype object at all times!

comments powered by Disqus