OOP in Javascript
Public/Private Variables and Methods
* private variables are declared with the 'var' keyword inside the object, and can only be accessed by private functions and privileged methods.
* private functions are declared inline inside the object's constructor (or alternatively may be defined via var functionName=function(){...}) and may only be called by privileged methods (including the object's constructor).
* privileged methods are declared with this.methodName=function(){...} and may invoked by code external to the object.
* public properties are declared with this.variableName and may be read/written from outside the object.
* public methods are defined by Classname.prototype.methodName = function(){...} and may be called from outside the object.
* prototype properties are defined by Classname.prototype.propertyName = someValue
* static properties are defined by Classname.propertyName = someValue
Inheritance
* You cause a class to inherit using ChildClassName.prototype = new ParentClass();.
* You need to remember to reset the constructor property for the class using ChildClassName.prototype.constructor=ChildClassName.
* You can call ancestor class methods which your child class has overridden using the Function.call() method.
* Javascript does not support protected methods.
Every object instance in JS has a property named constructor that points to its parent class. For example, someAnimal.constructor==Mammmal is true. Armed with this knowledge, we can remake the haveABaby() method like this:
Calling 'super' methods
Javascript does not have any sort of 'super' property, which would point to its parent class. Instead, you use the call() method of a Function object, which allows you to run a function using a different object as context for it. If you needed to pass parameters to this function, they would go after the 'this'.
Rather than having to know that Cat inherits from Mammal, and having to type in Mammal.prototype each time you wanted to call an ancestor method, wouldn't it be nice to have your own property of the cat pointing to its ancestor class?
Spoofing pure virtual classes
Some OOP languages have the concept of a pure virtual class...one which cannot be instantiated itself, but only inherited from. For example, you might have a LivingThing class which Mammal inherited from, but you didn't want someone to be able to make a LivingThing without specifying what type of thing it was. You can do this in JS by making the virtual class an object instead of a function.
With the above, doing something like var spirit = new LivingThing() would result in an error, since LivingThing is not a function, and hence can't be used as a constructor.
Convenient Inheritance
Rather than writing 3 lines every time you want to inherit one class from another, it's convenient to extend the Function object to do it for you:
Just make sure you call this method immediately after your constructor, before you extend the prototype for the object.
* private variables are declared with the 'var' keyword inside the object, and can only be accessed by private functions and privileged methods.
* private functions are declared inline inside the object's constructor (or alternatively may be defined via var functionName=function(){...}) and may only be called by privileged methods (including the object's constructor).
* privileged methods are declared with this.methodName=function(){...} and may invoked by code external to the object.
* public properties are declared with this.variableName and may be read/written from outside the object.
* public methods are defined by Classname.prototype.methodName = function(){...} and may be called from outside the object.
* prototype properties are defined by Classname.prototype.propertyName = someValue
* static properties are defined by Classname.propertyName = someValue
function Person(n,race){ this.constructor.population++; // ************************************************************************ // PRIVATE VARIABLES AND FUNCTIONS // ONLY PRIVELEGED METHODS MAY VIEW/EDIT/INVOKE // *********************************************************************** var alive=true, age=1; var maxAge=70+Math.round(Math.random()*15)+Math.round(Math.random()*15); function makeOlder(){ return alive = (++age <= maxAge) } var myName=n?n:"John Doe"; var weight=1; // ************************************************************************ // PRIVILEGED METHODS // MAY BE INVOKED PUBLICLY AND MAY ACCESS PRIVATE ITEMS // MAY NOT BE CHANGED; MAY BE REPLACED WITH PUBLIC FLAVORS // ************************************************************************ this.toString=this.getName=function(){ return myName } this.eat=function(){ if (makeOlder()){ this.dirtFactor++; return weight*=3; } else alert(myName+" can't eat, he's dead!"); } this.exercise=function(){ if (makeOlder()){ this.dirtFactor++; return weight/=2; } else alert(myName+" can't exercise, he's dead!"); } this.weigh=function(){ return weight } this.getRace=function(){ return race } this.getAge=function(){ return age } this.muchTimePasses=function(){ age+=50; this.dirtFactor=10; } // ************************************************************************ // PUBLIC PROPERTIES -- ANYONE MAY READ/WRITE // ************************************************************************ this.clothing="nothing/naked"; this.dirtFactor=0; } // ************************************************************************ // PUBLIC METHODS -- ANYONE MAY READ/WRITE // ************************************************************************ Person.prototype.beCool = function(){ this.clothing="khakis and black shirt" } Person.prototype.shower = function(){ this.dirtFactor=2 } Person.prototype.showLegs = function(){ alert(this+" has "+this.legs+" legs") } Person.prototype.amputate = function(){ this.legs-- } // ************************************************************************ // PROTOTYOPE PROERTIES -- ANYONE MAY READ/WRITE (but may be overridden) // ************************************************************************ Person.prototype.legs=2; // ************************************************************************ // STATIC PROPERTIES -- ANYONE MAY READ/WRITE // ************************************************************************ Person.population = 0; // Here is the code that uses the Person class function RunGavinsLife(){ var gk=new Person("Gavin","caucasian"); //New instance of the Person object created. var lk=new Person("Lisa","caucasian"); //New instance of the Person object created. alert("There are now "+Person.population+" people"); gk.showLegs(); lk.showLegs(); //Both share the common 'Person.prototype.legs' variable when looking at 'this.legs' gk.race = "hispanic"; //Sets a public variable, but does not overwrite private 'race' variable. alert(gk+"'s real race is "+gk.getRace()); //Returns 'caucasian' from private 'race' variable set at create time. gk.eat(); gk.eat(); gk.eat(); //weight is 3...then 9...then 27 alert(gk+" weighs "+gk.weigh()+" pounds and has a dirt factor of "+gk.dirtFactor); gk.exercise(); //weight is now 13.5 gk.beCool(); //clothing has been update to current fashionable levels gk.clothing="Pimp Outfit"; //clothing is a public variable that can be updated to any funky value gk.shower(); alert("Existing shower technology has gotten "+gk+" to a dirt factor of "+gk.dirtFactor); gk.muchTimePasses(); //50 Years Pass Person.prototype.shower=function(){ //Shower technology improves for everyone this.dirtFactor=0; } gk.beCool=function(){ //Gavin alone gets new fashion ideas this.clothing="tinfoil"; }; gk.beCool(); gk.shower(); alert("Fashionable "+gk+" at " +gk.getAge()+" years old is now wearing " +gk.clothing+" with dirt factor " +gk.dirtFactor); gk.amputate(); //Uses the prototype property and makes a public property gk.showLegs(); lk.showLegs(); //Lisa still has the prototype property gk.muchTimePasses(); //50 Years Pass...Gavin is now over 100 years old. gk.eat(); //Complains about extreme age, death, and inability to eat. }
Inheritance
* You cause a class to inherit using ChildClassName.prototype = new ParentClass();.
* You need to remember to reset the constructor property for the class using ChildClassName.prototype.constructor=ChildClassName.
* You can call ancestor class methods which your child class has overridden using the Function.call() method.
* Javascript does not support protected methods.
function Mammal(name){ this.name=name; this.offspring=[]; } Mammal.prototype.haveABaby=function(){ var newBaby=new Mammal("Baby "+this.name); this.offspring.push(newBaby); return newBaby; } Mammal.prototype.toString=function(){ return '[Mammal "'+this.name+'"]'; } Cat.prototype = new Mammal(); // Here's where the inheritance occurs Cat.prototype.constructor=Cat; // Otherwise instances of Cat would have a constructor of Mammal function Cat(name){ this.name=name; } Cat.prototype.toString=function(){ return '[Cat "'+this.name+'"]'; } var someAnimal = new Mammal('Mr. Biggles'); var myPet = new Cat('Felix'); alert('someAnimal is '+someAnimal); // results in 'someAnimal is [Mammal "Mr. Biggles"]' alert('myPet is '+myPet); // results in 'myPet is [Cat "Felix"]' myPet.haveABaby(); // calls a method inherited from Mammal alert(myPet.offspring.length); // shows that the cat has one baby now alert(myPet.offspring[0]); // results in '[Mammal "Baby Felix"]'
Every object instance in JS has a property named constructor that points to its parent class. For example, someAnimal.constructor==Mammmal is true. Armed with this knowledge, we can remake the haveABaby() method like this:
Mammal.prototype.haveABaby=function(){ var newBaby=new this.constructor("Baby "+this.name); this.offspring.push(newBaby); return newBaby; } ... myPet.haveABaby(); // Same as before: calls the method inherited from Mammal alert(myPet.offspring[0]); // Now results in '[Cat "Baby Felix"]'
Calling 'super' methods
Javascript does not have any sort of 'super' property, which would point to its parent class. Instead, you use the call() method of a Function object, which allows you to run a function using a different object as context for it. If you needed to pass parameters to this function, they would go after the 'this'.
Cat.prototype.haveABaby=function(){ Mammal.prototype.haveABaby.call(this); alert("mew!"); }
Rather than having to know that Cat inherits from Mammal, and having to type in Mammal.prototype each time you wanted to call an ancestor method, wouldn't it be nice to have your own property of the cat pointing to its ancestor class?
Cat.prototype = new Mammal(); Cat.prototype.constructor=Cat; Cat.prototype.parent = Mammal.prototype; ... Cat.prototype.haveABaby=function(){ var theKitten = this.parent.haveABaby.call(this); alert("mew!"); return theKitten; }
Spoofing pure virtual classes
Some OOP languages have the concept of a pure virtual class...one which cannot be instantiated itself, but only inherited from. For example, you might have a LivingThing class which Mammal inherited from, but you didn't want someone to be able to make a LivingThing without specifying what type of thing it was. You can do this in JS by making the virtual class an object instead of a function.
LivingThing = { beBorn : function(){ this.alive=true; } } ... Mammal.prototype = LivingThing; Mammal.prototype.parent = LivingThing; //Note: not 'LivingThing.prototype' Mammal.prototype.haveABaby=function(){ this.parent.beBorn.call(this); var newBaby=new this.constructor("Baby "+this.name); this.offspring.push(newBaby); return newBaby; }
With the above, doing something like var spirit = new LivingThing() would result in an error, since LivingThing is not a function, and hence can't be used as a constructor.
Convenient Inheritance
Rather than writing 3 lines every time you want to inherit one class from another, it's convenient to extend the Function object to do it for you:
Function.prototype.inheritsFrom = function( parentClassOrObject ){ if ( parentClassOrObject.constructor == Function ) { //Normal Inheritance this.prototype = new parentClassOrObject; this.prototype.constructor = this; this.prototype.parent = parentClassOrObject.prototype; } else { //Pure Virtual Inheritance this.prototype = parentClassOrObject; this.prototype.constructor = this; this.prototype.parent = parentClassOrObject; } return this; } // // LivingThing = { beBorn : function(){ this.alive = true; } } // // function Mammal(name){ this.name=name; this.offspring=[]; } Mammal.inheritsFrom( LivingThing ); Mammal.prototype.haveABaby=function(){ this.parent.beBorn.call(this); var newBaby = new this.constructor( "Baby " + this.name ); this.offspring.push(newBaby); return newBaby; } // // function Cat( name ){ this.name=name; } Cat.inheritsFrom( Mammal ); Cat.prototype.haveABaby=function(){ var theKitten = this.parent.haveABaby.call(this); alert("mew!"); return theKitten; } Cat.prototype.toString=function(){ return '[Cat "'+this.name+'"]'; } // // var felix = new Cat( "Felix" ); var kitten = felix.haveABaby( ); // mew! alert( kitten ); // [Cat "Baby Felix"]
Just make sure you call this method immediately after your constructor, before you extend the prototype for the object.