/ Published in: JavaScript
                    
                                        
An implementation of class inheritance in JavaScript.
                
                            
                                Expand |
                                Embed | Plain Text
                            
                        
                        Copy this code and paste it in your HTML
/*****************************************
* An implementation of class inheritance
*
* This work is licensed under a Creative Commons Attribution 3.0 Unported License
* http://creativecommons.org/licenses/by/3.0/
*
* Author: Andy Harrison, http://dragonzreef.com/
* Date: 14 December 2011
*****************************************/
//techniques and inspiration mainly from:
// http://ejohn.org/blog/simple-javascript-inheritance/
// http://joost.zeekat.nl/constructors-considered-mildly-confusing.html
var Class = (function(){
"use strict";
function Class(){} //the base class
Class.prototype.toString = function(){ return "[object Class]"; };
Class.toString = function(){ return "[class Class]"; };
//function to create a new class that inherits from this class
//options argument must be an object. Possible options:
// className: string used in .toString() for the constructor function, its prototype, and instances of the new class
// init: function used to initialize a new instance of the class
// extensions: object containing additional/overriding properties and methods for the new class
// return: option 1: function used to return a value when the constructor is called without the `new` keyword
// option 2: value to be returned when the constructor is called without the `new` keyword
Class.extend = function(options)
{
if(!options){
options = {};
}
if(options.init && typeof options.init !== "function"){
delete options.init;
}
if(typeof options.return !== "function"){ //if a function was not passed for options.return
//make options.return into a function returning that value (undefined or otherwise)
options.return = (function (retVal){ return function (){ return retVal; }; })(options.return);
}
/*** variables ***/
var newPrototype, name, superPrototype = this.prototype, newProp;
/*** functions ***/
var emptyFn = function(){};
var usesSuper;
if((/foo/).test(function(){foo;})){ //browser check: if it allows the decompilation of functions (i.e., you can get the code as a string)
usesSuper = function(fn){ return (/\b_super\b/).test(fn); };
}
else{
usesSuper = function(){ return true; }; //can't tell, so assume the function uses `_super`
}
function addSuper(newFn, superFn)
{
return function()
{
//note: in this function, `this` refers to the new prototype
var tmp = this._super; //save the current value of ._super (in case the object has this property/method)
//temporarily add a new ._super() method that is the overridden function on the super-class
this._super = superFn;
//execute the new function
var ret = newFn.apply(this, arguments);
this._super = tmp; //restore this._super
return ret;
};
}
/*** the rest ***/
emptyFn.prototype = this.prototype;
newPrototype = new emptyFn(); //uninitialized instance of the super-class will be the prototype of the sub-class
if(options.className){
newPrototype.toString = function(){ return "[object "+options.className+"]"; }; //override .toString()
}
//add the new/overriding methods & properties
if(options.extensions){
for(name in options.extensions)
{
if(options.extensions.hasOwnProperty(name)){
newProp = options.extensions[name];
//if we're overwriting an existing function that uses `_super` in its code
if(typeof newProp === "function" && typeof superPrototype[name] === "function" && usesSuper(newProp)){
newPrototype[name] = addSuper(newProp, superPrototype[name]); //use a modified function where `this._super` refers to the overridden function
}
else{
newPrototype[name] = newProp;
}
}
}
}
//if the initialization function uses `_super`
if(options.init && usesSuper(options.init)){
options.init = addSuper(options.init, this); //use a modified function where `this._super` refers to the super-class (constructor)
}
//create the new class
function Class()
{
if(this && this instanceof Class){ //if a new instance is being created (i.e., the `new` keyword is being used)
//note: this condition will also be true in the odd case that this constructor is called in the context of an instance of itself. e.g.:
// var X = Class.extend({});
// var y = new X();
// y.z = X;
// y.z(); //this condition will now be true, even though the `new` keyword is not being used
this.constructor = Class; //this function is the constructor for the new instance (not the constructor of the prototype)
if(options.init){
options.init.apply(this, arguments);
}
}
else{
return options.return();
}
}
Class.prototype = newPrototype;
Class.extend = this.extend; //make extend() a method of the new class
//e.g.,
// var Foo = Class.extend({});
// var Bar = Foo.extend({});
// instead of
// var Foo = Class.extend({});
// var Bar = Class.extend.call(Foo, {});
if(options.className){
Class.toString = function(){ return "[class "+options.className+"]"; };
}
return Class;
};
return Class;
})();
/*********************************************/
/*************** Example Usage ***************/
/*********************************************/
/*
var Pack = (function(){
var allPacks = [];
var Pack = Class.extend({
className: "Pack",
init: function(id, firstMember){
this.packID = id;
var wolves = [firstMember];
console.log("Pack \""+id+"\" has its first member.");
this.packSize = function(){return wolves.length},
this.addMember = function(wolf){
wolves.push(wolf);
console.log("Pack \""+this.packID+"\" has a new member.");
}
allPacks.push(this);
},
extensions: {
packID: ""
},
return: function(){
return allPacks.length;
}
});
Pack.packs = function(){return allPacks.length};
Pack.allPacks = function(){return allPacks};
return Pack;
})();
var Wolf = Class.extend({
className: "Wolf",
init: function(a){
var age = a;
console.log("A wolf has been spotted.");
this.age = function(){return age};
}
});
var Cub = Wolf.extend({
className: "Cub",
init: function(){
console.log("A cub is born!");
this._super(0); //call the initialization function of the Wolf class
}
});
function logPacks()
{
console.log("\nThere "+(Pack.packs()==1?"is 1 pack":"are "+Pack.packs()+" packs")+" in the world.")
console.log("Pack() returned "+Pack());
//if(pack) console.log("pack.test() returned "+pack.test()); //expected: undefined
var packs = Pack.allPacks();
for(var i=0; i<packs.length; i++)
{
console.log("Wolves in pack \""+packs[i].packID+"\": "+packs[i].packSize());
}
console.log("\n");
}
logPacks();
var wolf = new Wolf(4);
var pack = new Pack("foo", wolf);
pack.test = Pack;
logPacks();
wolf = new Cub();
pack.addMember(wolf);
logPacks();
wolf = new Wolf(4);
var pack2 = new Pack("bar", wolf);
logPacks();
//*/
Comments
 Subscribe to comments
                    Subscribe to comments
                
                