Here is my solution based on the standard prototype inheritance method described in.
First, I start by defining these helper methods that make it easier to read and read later:
Function.prototype.setSuperclass = function(target) { // Set a custom field for keeping track of the object 'superclass'. this._superclass = target; // Set the internal [[Prototype]] of instances of this object to a new object // which inherits from the superclass prototype. this.prototype = Object.create(this._superclass.prototype); // Correct the constructor attribute of this class prototype this.prototype.constructor = this; }; Function.prototype.getSuperclass = function(target) { // Easy way of finding out what a class inherits from return this._superclass; }; Function.prototype.callSuper = function(target, methodName, args) { // If methodName is ommitted, call the constructor. if (arguments.length < 3) { return this.callSuperConstructor(arguments[0], arguments[1]); } // `args` is an empty array by default. if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); var method = superclass.prototype[methodName]; if (typeof method != "function") throw new TypeError("TypeError: Object " + superclass.prototype + " has no method '" + methodName + "'"); return method.apply(target, args); }; Function.prototype.callSuperConstructor = function(target, args) { if (args === undefined || args === null) args = []; var superclass = this.getSuperclass(); if (superclass === undefined) throw new TypeError("A superclass for " + this + " could not be found."); return superclass.apply(target, args); };
Now, not only can you set the class superclass with SubClass.setSuperclass(ParentClass) , but you can also call overridden methods with SubClass.callSuper(this, 'functionName', [argument1, argument2...]) :
/** * Transform base class */ function Transform() { this.type = "2d"; } Transform.prototype.toString = function() { return "Transform"; } /** * Translation class. */ function Translation(x, y) { // Parent constructor Translation.callSuper(this, arguments); // Public properties this.x = x; this.y = y; } // Inheritance Translation.setSuperclass(Transform); // Override Translation.prototype.toString = function() { return Translation.callSuper(this, 'toString', arguments) + this.type + " Translation " + this.x + ":" + this.y; } /** * Rotation class. */ function Rotation(angle) { // Parent constructor Rotation.callSuper(this, arguments); // Public properties this.angle = angle; } // Inheritance Rotation.setSuperclass(Transform); // Override Rotation.prototype.toString = function() { return Rotation.callSuper(this, 'toString', arguments) + this.type + " Rotation " + this.angle; } // Tests translation = new Translation(10, 15); console.log(translation instanceof Transform); // true console.log(translation instanceof Translation); // true console.log(translation instanceof Rotation); // false console.log(translation.toString()) // Transform2d Translation 10:15
True, even with auxiliary functions, the syntax here is rather inconvenient. Fortunately, although syntax sugar (the most minimal classes ) was added in ECMAScript 6 to make things a lot prettier. For example:.
class Transform { constructor() { this.type = "2d"; } toString() { return "Transform"; } } class Translation extends Transform { constructor(x, y) { super();
Please note that ECMAScript 6 is still at the project stage, and as far as I know, it is not implemented in any major web browser. However, if you want, you can use something like the Traceur compiler to compile ECMAScript 6 down to the plain old ECMAScript 5 based on JavaScript. You can see the above example compiled using Traceur here .