Pseudo-classical Inheritance in JavaScript for Modules

JavaScript

The classical design of object in JavaScript can be expressed like:

var ConstructorFunc = function () {
    var _privateMember = "private member";
}
ConstructorFunc.prototype.publicMember = "private member";
ConstructorFunc.prototype.privilegedMethod = function () {
    return _privateMember;
};

Pretty clumsy, isn’t it? I prefer the module design propagated so indefatigably by Addy Osmani (http://addyosmani.com/largescalejavascript/):

var Module = function () {
    // Constructor's job
    var _privateMember = "private member";
    return {
        publicMember : "private member",
        privilegedMethod : function () {
            return _privateMember;
        }
    }
}

It looks much nicer to me. However, here shows up a problem. What about inheritance? Common implementation for inheritance doesn’t work on such objects:

Module.prototype = Object.create({ inheritedMember: true });
var o = new Module();
console.log(o instanceof Module); // false
console.log(typeof o.inheritedMember); // undefined

Why is it so? Let’s examine how objects are being instantiated, what happens when the new operator called:

  • A new object initialized
  • Internal prototype property assigned to that object. It has a link to the generating object, e.g. Function.prototype
  • After that the constructor function is called.
  • If the return value is a primitive, the object created internally will be the result of new. Otherwise, we end up with the object returned by constructor and internally created object and its prototype are lost.

And here we go, since module object constructor returns an object, module prototype is lost. Though if we control object creation we can restore its prototype.

Douglas Crockford proposed in 2008 following solution for classically designed objects

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
newObject = Object.create(oldObject);

It is also used as clone function (http://jsdesignpatterns.com/). Let us have a similar factory, but tuned for our own requirements:

Object.createFromModule = function (module) {
    var key, members = module.apply(this, arguments), Fn = function () {};
    Fn.prototype = module.prototype; // Link to the supertype
    for (key in members) { // Mix in members
        if (members.hasOwnProperty(key)) {
            Fn.prototype[key] = members[key];
        }
    }
    return new Fn();
};

It is not a good practice to modify first-class objects, but I couldn’t resist the temptation:

Function.prototype.createInstance = function () {
    var key, module = this, members = module.apply(this, arguments), Fn = function () {};
    Fn.prototype = module.prototype; // Link to the supertype
    for (key in members) { // Mix in members
        if (members.hasOwnProperty(key)) {
            Fn.prototype[key] = members[key];
        }
    }
    return new Fn();
};

Now we can check how it works on an example:

// Usage example

var AbstractModule = function () {
    return {
        inheritedProp : "inherited property",
        publicProp : "original property"
    };
},
    ConcreteModule = function () {
        var _privateVar = "private";
        return {
            getPrivate : function () {
                return _privateVar;
            },
            publicProp : "overriden property"
        };
    };
// ConcreteModule extends AbstractModule
ConcreteModule.prototype = AbstractModule.createInstance();

// Make an instance of ConcreteModule
var o = ConcreteModule.createInstance();

console.log(o.getPrivate()); // private
console.log(o.publicProp); // overriden property
console.log(o.constructor.prototype.publicProp); // original property

Everything looks nice, but the line where we extend one module by another.

ConcreteModule.prototype = AbstractModule.createInstance();

Thinking of a good API, something like following comes on mind:

ConcreteModule.extends(AbstractModule);

Not bad, but I rather won’t add one extra method into Function prototype. Besides, the original goal was to keep all the object describing information within its body, like it is in class-based programming. Thus, we change the factory a little bit:

Function.prototype.createInstance = function () {
        var key, module = this, members = module.apply(this, arguments), Fn = function () {};
        members.hasOwnProperty( "__extends__" ) %26%26 members[ "__extends__" ]
            %26%26 (module.prototype = members[ "__extends__" ].createInstance());
        Fn.prototype = module.prototype; // Link to the supertype
        for (key in members) { // Mix in members
            if (members.hasOwnProperty(key)) {
                Fn.prototype[key] = members[key];
            }
        }
        return new Fn();
    };

Now we just point in the public property extends the object we intend to extend and that’s it. Final code would be:

(function(){
    "use strict";

    Function.prototype.createInstance = function () {
        var key, module = this, members = module.apply(this, arguments), Fn = function () {};
        members.hasOwnProperty( "__extends__" ) %26%26 members[ "__extends__" ]
            %26%26 (module.prototype = members[ "__extends__" ].createInstance());
        Fn.prototype = module.prototype; // Link to the supertype
        for (key in members) { // Mix in members
            if (members.hasOwnProperty(key)) {
                Fn.prototype[key] = members[key];
            }
        }
        return new Fn();
    };

    // Usage example
    var AbstractModule = function () {
        return {
            inheritedProp : "inherited property",
            publicProp : "original property"
        };
    },
        ConcreteModule = function () {
            var _privateVar = "private";
            return {
                __extends__: AbstractModule, // ConcreteModule extends AbstractModule
                getPrivate : function () {
                    return _privateVar;
                },
                publicProp : "overriden property"
            };
        };

    // Make an instance of ConcreteModule
    var o = ConcreteModule.createInstance();

    console.log(o instanceof ConcreteModule); // true
    console.log(o instanceof AbstractModule); // true
    console.log(o.getPrivate()); // private
    console.log(o.publicProp); // overriden property
    console.log(o.constructor.prototype.publicProp); // original property

}());

Complete code