Classical Inheritance in JavaScript
I’ll start this post with the obligatory disclaimer; JavaScript works perfectly well without classical inheritance. If you don’t want to use it, that’s peachy – you’re done here. If you do, read on.
There are countless ways to apply classical inheritance to JavaScript; practically every JS library or framework out there uses one. Even though it is an old topic, I was looking at several solutions and none of them seemed quite right to me.
John Resig’s Simple JavaScript Inheritance came closest (so much so that a significant portion is reproduced in my code below); I especially liked his solution for accessing a superclass’ methods. That being said, I didn’t find it perfect – methods had to be defined at the same time as the constructor for the _super() method to be available. I also wasn’t keen on defering the actual construction to a seperate “init” method.
The code
So, here is my version – a few lines heavier but with more goodies.
// parts taken from JResig's Simple JS Inheritancevar Class=(function(){var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;var me=function Class(){};me.prototype.classname=function(){return this.constructor.name;};var supered=function(fn, obj, p){return fnTest.test(fn) ? function(){this._super=p ? obj._superclass.prototype[p] : obj;var r=fn.apply(this,arguments);delete this._super;return r;}: fn;};me.augment=function(){var i=arguments.length;while (i--){var o=arguments[i];for (var p in o) this.prototype[p] = (typeof o[p]=='function') ? supered(o[p], this, p) : o[p];}};me.extend=function(){var args=[].slice.call(arguments), sub=args.shift(), sup=args.length && typeof args[0]=="function" ? args[0] : null, p=sup ? null : args;var Inherit=function(){};Inherit.prototype=sup ? sup.prototype : this.prototype;sub=sup ? sub : supered(sub, this);sub.prototype=new Inherit();sub.prototype.constructor=sub;sub._superclass=sup || this;if (!sup){sub.extend=this.extend, sub.augment=this.augment;if (p && p.length) sub.augment.apply(sub,p);return sub;}else return this;};return me;})();
Basically, this creates a Class object. The main method is extend(), which can be used in two different ways:
Existing classes
This technique assumes you have already created two classes, and simply want to create an inheritance chain between the two. In this case, pass the subclass and superclass constructors to the extend() method:
var Circle=function(r){this.radius=r;};var Wheel=function (r,p){this.constructor._superclass.call(this,r);this.price=p;};Class.extend(Wheel, Circle);Circle.prototype.area=function(){return Math.PI*(this.radius*this.radius);};var w=new Wheel(4,10);w.area(); // => 50.26...
The inheritance chain is set up, with minimal disruption to the existing classes. The only addition is to the subclass constructor, which is given a _superclass property pointing to the superclass. Fairly simple so far.
The alternate method
Rather than creating distinct classes then extending them, with the second technique we create classes by subclassing the Class object. This gives us several benefits, including access to the superclass’ methods, even in methods added after the class definition.
To do this, pass the ‘base’ class constructor to the first argument of extend(). You may pass any number of further object arguments, whose properties will be added as prototype methods/properties of the class. The subclass will be returned:
var Circle=Class.extend(function Circle(r){this.radius=r;},{area:function(){return Math.PI*(this.radius*this.radius);}});var c=new Circle(4);c.area(); // => 50.26...c.classname(); // => 'Circle'
By passing a named function as the constructor, the classname() method (inherited from Class) returns the name of the instance’s class.
We can then extend Circle. With this technique, we can call the superclass’ constructor or prototype methods inside any other method with _super():
var Wheel=Circle.extend(function (r,p){this._super(r); // call Circle's constructorthis.price=p;});Wheel.augment({area:function(){return this._super()+10; // doesn't make sense, just an example!}});var w=new Wheel(4,10);
Note that we can use augment() to add further prototype methods, and _super() is available within these methods too.
Conclusion
Obviously you wouldn’t want to use both of these techniques in the same script; confusion would doubtlessly ensue. But the solution is flexible, and gives you control of the complexity of the inheritance chain you want to create.