Showing posts with label oop. Show all posts
Showing posts with label oop. Show all posts

Sunday, 25 December 2011

JS OOP

If you thought that JS is written only using functions and objects, well, you are kind of wright :). But actually it is the most permissive (in my opinion) OOP language.
A class in js is defined using "function" and when we use something like:
function sum(a,b){
    return a+b;
}
we actually create a new class named "sum". All that we see inside the "{...}" is actually the constructor, so when we call
var c = sum(1,2)
c will be equal to 3, because that is what the constructor of the class returns, the sum of the variables.
If we would use
var c = new sum(1,2);
c would be an instance of the class sum. Of course for the purpose of simplicity class sum dose not have any variables or methods attached to it, so let's go ahead and make this a little more complex.
Let's define a new class that we can also use as a standalone function.

function Animal(name,species){
    // standard (for me) way to use default values for params
    // also the params will be private vars in our class
    name = name || "Unnamed";
    species = species || "Unknown";

    // setup some public vars
    this.animalTitle = name + ' the ' + species;

    // setup some private vars
    var age = 0;

    // create a private methos
    var grow = function(years){
        return age + years;
    }

    // some public methods(functions :) )
    this.getOlder = function(how_much){
        // here we access the private var
        // and a public one
        how_much = how_much || 0;
        age = grow(how_much);
        this.animalTitle = name + ' the ' + species + ' at ' + age + ' years old';
    }

    // using return to demonstrate how it works as a simple function
    return this.animalTitle;
}
Ok, now let's explain more.
name = name || 'Unnamed' is the same as if(name == null) name = 'Unnamed';. BTW, if you expect name, or whatever the param name will be, to be a bool or number you should use ==null or name = (name == null ? "Unnamed" || name) because 0 or false will make act in the expression used by me the same way as null.
this.animalTitle is a public variable that will be readable/writable, will see later.
age is a private variable that can only be used inside the class (also as I said in the inline comments, the params are also considered private vars).
grow is a private method, a function that can be called only inside the class definition.
And getOlder is a public method that can be called or overwritten.
Here is a example on how this can be used as a function:

var my_pet = Animal("Rex","Dog");
alert(my_pet);
// will alert "Rex the Dog"
or as a class definition to create an object:
var my_pet = new Animal("Simba","Lion");
alert(my_pet);
// will alert [object Object]
// to get a better understanding of what this is we can use GC console ot FF firebug and do:
console.log(my_pet)
alert(my_pet.animalTitle)
// will alert 'Simba the Lion'
my_pet.getOlder(10); // calling a public method
alert(my_pet.animalTitle)
// will alert 'Simba the Lion 10 years old'
my_pet.animalTitle = 'Custom title for my pet'; // overwriting a public member of the object
alert(my_pet.animalTitle)
// will alert 'Custom title for my pet'
Next we should talk about the prototype object.
Most of the js community talks about the prototype object as giving the functionability to add public methods/vars to classes. I actually don't like how this sounds and I will refraze it like this: "The prototype object is used to extend methods and variables of a class with the downside that you won't be able to use the private vars". Ok, now let's get back to some code:

// using our class Animal
Animal.prototype.whatToSay = 'I can say this:';
var my_pet = new Animal();
alert(my_pet.whatToSay);
// will alert 'I can say this:'

// we want to add a method named speak
Animal.prototype.speak = function(){
    // we can use public methods from our constructor and also ones we defined with prototype
    alert(this.whatToSay + ' My name is ' + this.animalTitle);
}
// we can't use age or grow() here because we don't have access to anything
// not defined in the constructor (function) with this. (public stuff)
var my_pet2 = new Animal();
my_pet2.speak();
// will alert 'I can say this: I am Unknown the Unnamed'
// why Unnamed? because we didn't gave him a name when we created him and this is the
// default string we gave it
var my_pet3 = new Animal('Bobo','Bear');
my_pet3.speak();
// will alert 'I can say this: I am Bobo the Bear'
Hope this will help somebody on a better understanding of the OOP in JS. Will continue with object members and inheritance.

Sunday, 25 September 2011

JS Classes


First step(Basic class definition)

  • 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 Mammal(name){
	//private var
	var id;
	//private method
	var set_id = function(){
		id = Mammal.id++;
	}
	//public property
	this.name=name;
	this.offspring=[];
	//privileged method
	this.getId = function(){
		return id;
	}
	//constructor
	set_id();
}
//public methods
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+'"]';
}
//static property
Mammal.id = 1;

Second step(Inheritance)

  • You cause a class to inherit using ChildClassName.prototype = newParentClass();.
  • 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 .

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"]' 

Using the .constructor property
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]); 

Calling 'super' methods

Cat.prototype.haveABaby=function(){ 
	Mammal.prototype.haveABaby.call(this);
	alert("mew!");
}

Making your own 'super' property

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

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;
} 

Convenient Inheritance

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"]