JavaScript classes and inheritance post

12/13/2014 with tags : javascript

JavaScript is a simple language. But it's quite complicated to fully understand how it works and to see its full potential. Its even more complicated for people who are are using OOP in its classical form (the base class is being extended to create a new class). And a challenge here is to change the way you thing about an 'object' and 'inheritance'.

In order to better understand how can I use inheritance in JavaScript I have read a lot of articles, code examples. At some point a came across Backbone. Library defines an extend function which is used to created objects. I found a best explaination of how it works in a book 'JavaScript Patterns' by Stoyan Stefanov.

Class object definition

Here is my version of this function, that makes it possible to extend classes and to use method overriding.

This code is based on Backbone extend and is using Underscore

 var DummyCtor = function(){};

 // base `class`
 var Class = function () {};

 // instance init method
 Class.prototype.construct = function() {};

 // a way to introduce method overriding - call super method
 Class.prototype._super = function(name,args) {
    var f = this.__super__[name];
    return f ? f.apply( this, _.isArray(args)?args:[args] ) : null;
 };

 // `extend` class
 Class.extend = function(instanceProps, staticProps) 
 {
        // `class` to extend
        var parent = this;

        // object instance init method - user provided or empty function
        var ctor = (instanceProps && instanceProps.hasOwnProperty('construct')) ? instanceProps['construct'] : DummyCtor;

        // `new class` constructor will call super `class` constructor
        var child = function(){
            parent.apply(this, arguments);
            ctor.apply(this, arguments);
        };

        // static methods
        _.extend(child, parent, staticProps);

        // Temporary constructor pattern (1*)
        // (naming borrowed from Backbone)
        var Surrogate = function(){};
        Surrogate.prototype = parent.prototype;

        var surrogate = new Surrogate();
        // keep track of super `class`
        surrogate.__super__ = parent.prototype;
        // update constructor pointer
        surrogate.constructor = child;
        // setup prototype chain
        child.prototype = surrogate;

        // new `class` instance methods
        _.extend(child.prototype, instanceProps);

        return child;
    };

This pattern is incomplete, please read an updated post

Inheritance

I took me while to fully understand the magic behind a temporary constructor pattern. First step is to create a empty function which serves as a proxy between new 'class' and the base 'class'. Think of it as a 'class object'.

    var Surrogate = function(){};

Set proxy prototype to a prototype of a base class. This will setup a 'class object' prototype chain between new 'class' definition and a base 'class' definition.

    Surrogate.prototype = parent.prototype;

Next create an instance of a proxy object

    var surrogate = new Surrogate();

Keep track of a base 'class' to allow method overriding. We will use this property in 'super' mehod lookup process.

    surrogate.__super__ = parent.prototype;

Update constructor pointer. This step makes it possible to create an object using this.constructor

    surrogate.constructor = child;

Finally setup prototype chain for new 'class'. At this point we have a new class with all methods of a base class, we can use instanceof on it to test inheritance and even use this.constructor to create object copies.

    child.prototype = surrogate;

'Why to use Surrogate'

First question that comes to mind is - why to use a proxy function instead of using base class prototype directly (as in example below)?

    child.prototype = parent.prototype;

Its fine. The only drawback is when child 'class' modifies prototype, change is also visible for base 'class'. For example when you add a method in child 'class' it will also be visible in base 'class'.

Inheritance example

var A = Class.extend({
    construct: function() {
        this.value = 1;
        console.log(">> A construct");
    },
    inc: function() {
        this.value += 1;
    }
});

var B = A.extend({
    construct: function() {
        // `class` A construct method will be called here
        console.log(">> B construct");  
    },
    inc: function() {
        this._super('inc');
        this.value += 1;
    }
});

var b = new B(); // >> A construct \n >> B construct
console.log(b instanceof A); // true
b.inc(); // will call inc in super `class`
console.log(b.value); // 3

var clone_b = new b.constructor(); // >> A construct \n >> B construct
console.log(clone_b instanceof B); // true
console.log(clone_b instanceof A); // true