/ 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(); //*/