JavaScript Classes


/ Published in: JavaScript
Save to your folder(s)

An implementation of class inheritance in JavaScript.


Copy this code and paste it in your HTML
  1. /*****************************************
  2.  * An implementation of class inheritance
  3.  *
  4.  * This work is licensed under a Creative Commons Attribution 3.0 Unported License
  5.  * http://creativecommons.org/licenses/by/3.0/
  6.  *
  7.  * Author: Andy Harrison, http://dragonzreef.com/
  8.  * Date: 14 December 2011
  9.  *****************************************/
  10.  
  11. //techniques and inspiration mainly from:
  12. // http://ejohn.org/blog/simple-javascript-inheritance/
  13. // http://joost.zeekat.nl/constructors-considered-mildly-confusing.html
  14.  
  15. var Class = (function(){
  16.  
  17. "use strict";
  18.  
  19. function Class(){} //the base class
  20. Class.prototype.toString = function(){ return "[object Class]"; };
  21. Class.toString = function(){ return "[class Class]"; };
  22.  
  23. //function to create a new class that inherits from this class
  24. //options argument must be an object. Possible options:
  25. // className: string used in .toString() for the constructor function, its prototype, and instances of the new class
  26. // init: function used to initialize a new instance of the class
  27. // extensions: object containing additional/overriding properties and methods for the new class
  28. // return: option 1: function used to return a value when the constructor is called without the `new` keyword
  29. // option 2: value to be returned when the constructor is called without the `new` keyword
  30. Class.extend = function(options)
  31. {
  32. if(!options){
  33. options = {};
  34. }
  35. if(options.init && typeof options.init !== "function"){
  36. delete options.init;
  37. }
  38. if(typeof options.return !== "function"){ //if a function was not passed for options.return
  39. //make options.return into a function returning that value (undefined or otherwise)
  40. options.return = (function (retVal){ return function (){ return retVal; }; })(options.return);
  41. }
  42.  
  43. /*** variables ***/
  44.  
  45. var newPrototype, name, superPrototype = this.prototype, newProp;
  46.  
  47. /*** functions ***/
  48.  
  49. var emptyFn = function(){};
  50.  
  51. var usesSuper;
  52. if((/foo/).test(function(){foo;})){ //browser check: if it allows the decompilation of functions (i.e., you can get the code as a string)
  53. usesSuper = function(fn){ return (/\b_super\b/).test(fn); };
  54. }
  55. else{
  56. usesSuper = function(){ return true; }; //can't tell, so assume the function uses `_super`
  57. }
  58.  
  59. function addSuper(newFn, superFn)
  60. {
  61. return function()
  62. {
  63. //note: in this function, `this` refers to the new prototype
  64.  
  65. var tmp = this._super; //save the current value of ._super (in case the object has this property/method)
  66.  
  67. //temporarily add a new ._super() method that is the overridden function on the super-class
  68. this._super = superFn;
  69.  
  70. //execute the new function
  71. var ret = newFn.apply(this, arguments);
  72.  
  73. this._super = tmp; //restore this._super
  74.  
  75. return ret;
  76. };
  77. }
  78.  
  79. /*** the rest ***/
  80.  
  81. emptyFn.prototype = this.prototype;
  82. newPrototype = new emptyFn(); //uninitialized instance of the super-class will be the prototype of the sub-class
  83.  
  84. if(options.className){
  85. newPrototype.toString = function(){ return "[object "+options.className+"]"; }; //override .toString()
  86. }
  87.  
  88. //add the new/overriding methods & properties
  89. if(options.extensions){
  90. for(name in options.extensions)
  91. {
  92. if(options.extensions.hasOwnProperty(name)){
  93. newProp = options.extensions[name];
  94.  
  95. //if we're overwriting an existing function that uses `_super` in its code
  96. if(typeof newProp === "function" && typeof superPrototype[name] === "function" && usesSuper(newProp)){
  97. newPrototype[name] = addSuper(newProp, superPrototype[name]); //use a modified function where `this._super` refers to the overridden function
  98. }
  99. else{
  100. newPrototype[name] = newProp;
  101. }
  102.  
  103. }
  104. }
  105. }
  106.  
  107. //if the initialization function uses `_super`
  108. if(options.init && usesSuper(options.init)){
  109. options.init = addSuper(options.init, this); //use a modified function where `this._super` refers to the super-class (constructor)
  110. }
  111.  
  112. //create the new class
  113. function Class()
  114. {
  115. if(this && this instanceof Class){ //if a new instance is being created (i.e., the `new` keyword is being used)
  116. //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.:
  117. // var X = Class.extend({});
  118. // var y = new X();
  119. // y.z = X;
  120. // y.z(); //this condition will now be true, even though the `new` keyword is not being used
  121. this.constructor = Class; //this function is the constructor for the new instance (not the constructor of the prototype)
  122. if(options.init){
  123. options.init.apply(this, arguments);
  124. }
  125. }
  126. else{
  127. return options.return();
  128. }
  129. }
  130. Class.prototype = newPrototype;
  131. Class.extend = this.extend; //make extend() a method of the new class
  132. //e.g.,
  133. // var Foo = Class.extend({});
  134. // var Bar = Foo.extend({});
  135. // instead of
  136. // var Foo = Class.extend({});
  137. // var Bar = Class.extend.call(Foo, {});
  138. if(options.className){
  139. Class.toString = function(){ return "[class "+options.className+"]"; };
  140. }
  141.  
  142. return Class;
  143. };
  144.  
  145. return Class;
  146.  
  147. })();
  148.  
  149. /*********************************************/
  150. /*************** Example Usage ***************/
  151. /*********************************************/
  152. /*
  153. var Pack = (function(){
  154. var allPacks = [];
  155. var Pack = Class.extend({
  156. className: "Pack",
  157. init: function(id, firstMember){
  158. this.packID = id;
  159. var wolves = [firstMember];
  160. console.log("Pack \""+id+"\" has its first member.");
  161. this.packSize = function(){return wolves.length},
  162. this.addMember = function(wolf){
  163. wolves.push(wolf);
  164. console.log("Pack \""+this.packID+"\" has a new member.");
  165. }
  166. allPacks.push(this);
  167. },
  168. extensions: {
  169. packID: ""
  170. },
  171. return: function(){
  172. return allPacks.length;
  173. }
  174. });
  175. Pack.packs = function(){return allPacks.length};
  176. Pack.allPacks = function(){return allPacks};
  177. return Pack;
  178. })();
  179.  
  180. var Wolf = Class.extend({
  181. className: "Wolf",
  182. init: function(a){
  183. var age = a;
  184. console.log("A wolf has been spotted.");
  185. this.age = function(){return age};
  186. }
  187. });
  188. var Cub = Wolf.extend({
  189. className: "Cub",
  190. init: function(){
  191. console.log("A cub is born!");
  192. this._super(0); //call the initialization function of the Wolf class
  193. }
  194. });
  195.  
  196. function logPacks()
  197. {
  198. console.log("\nThere "+(Pack.packs()==1?"is 1 pack":"are "+Pack.packs()+" packs")+" in the world.")
  199. console.log("Pack() returned "+Pack());
  200. //if(pack) console.log("pack.test() returned "+pack.test()); //expected: undefined
  201. var packs = Pack.allPacks();
  202. for(var i=0; i<packs.length; i++)
  203. {
  204. console.log("Wolves in pack \""+packs[i].packID+"\": "+packs[i].packSize());
  205. }
  206. console.log("\n");
  207. }
  208. logPacks();
  209. var wolf = new Wolf(4);
  210. var pack = new Pack("foo", wolf);
  211. pack.test = Pack;
  212. logPacks();
  213. wolf = new Cub();
  214. pack.addMember(wolf);
  215. logPacks();
  216. wolf = new Wolf(4);
  217. var pack2 = new Pack("bar", wolf);
  218. logPacks();
  219. //*/

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.