Introduction
In this article we will discuss how to mimic class
like structure in JavaScript
so that we can encapsulate related state and behavior as a module/class.
Background
Before going to creating class in JavaScript, we need to know what are closures and how they come to rescue in creating classes.
A famous saying about closures - " ..... Nested functions have access to vars and parameters of parent function event after parent function is executed/returned "
, and now what that really mean :)
Lets dig into that by seeing an example. Say we have a function that returns milliseconds of current time, when we call it it will returns lets asssume 345 milliseconds, when you call same function with some delay using setTimeOut of 30 milliseconds , the ouput would be different that the previous one , so state is not persisted in two calls.
Non closure demo
function time(){
var timeNow = new Date();
return timeNow.getMilliseconds();
};
Now when you call the above function two times with delay as follows:
alert(time());
setTimeout(function(){
alert(time());
},220);
It will alert different milliseconds as expected , since every time you call the function new Date()
object is instantiated and gives different milliseconds. How to solve the issue? Here comes the closures.
By returning a nested function you can create a closure so that even after you parent function is returned the nested function still has access to the vars
of parent function so when you first time call the function , it will return a nested function which will store the value of milliseconds and next time when you can use the returned nested function to access your state which is persisted.
Closure Demo
function timeClosure(){
var timeNow = new Date();
return function(){
return timeNow.getMilliseconds();
}
};
Now when you call the timeClosure()
and assign it to a variable, the timeClosure functions returns a nested function which will have access to timeNow variable saved. So now when you use the assigned variable two times even with delay also you will get same milliseconds as the returned nested function has access to the var
of parent function and it has saved state which is used twice , so new instantiation of Date()
object, so same Date()
object is used twice hence we will get same milliseconds two times.
Now when call the above function two times with delay as follows:
var nestedFunc = timeClosure();
alert(nestedFunc());
setTimeout(function(){
alert(nestedFunc());
},220);
Both will popup same milliseconds as explained above. Now does this mimic class like structure not really, because the state is persisted but you can't new it to have many instances of same structure. So here comes the patterns to rescue.
In this article we will discuss about two patterns:
Revealing module pattern
. Revealing prototype pattern
.
Revealing module pattern :
In this pattern we encapusalate fields and members inside a function and return an object literal to expose those members. The return of object literal here creates a closure around the members.
Lets see a pratical example to get context:
function Circle(radius){
var radius=radius;
var pi=Math.PI;
var area=function(){
return pi*(Math.pow(radius,2));
};
return{
area:area
}
}
Now this is class mimic , now you can instansiate above class as follows:
var circle1 = new Circle(10);
alert(circle1.area());
This will pop you area of the circle with radius 10 i..e 314.1592653589793
This pattern has one con that , the inner methods that we create are copied to each instance of the Circle class which is bit different from C# or Java world, in javascript methods will be copied to each instance that you create. To rescue it we will go with revealing prototype patterns which uses both revealing module pattern features with prototype feature.
Revealing prototype pattern :
In this module we have two sections ,
- Constructor part.
- Prototype part.
You specify all your public fields in constructor and private fields and methods in prototype part.
Lets see a pratical example to get context:
function Circle(radius){
this.radius=radius;
}
Circle.prototype=function(){
var pi = Math.PI;
var area = function(){
return pi*(Math.pow(this.radius,2));
};
return {
area:area
}
}();
Now you can instantiate and access area of the circle as follows:
var circle1 = new Circle(10);
alert(circle1.area());
This will popup you area of a circle with radius 10 which will be same as above :)
While coming to the above code, we have created a constructor and a prototype, prototype lets you to have methods shared across all instances instead of having a seperate copy for each instance of the class. And the by using self executed anonymous function we have added proptype pattern to have capability to encapusulate private fields and expose only public methods. So the return and the members in prototype patterns creates closure around your class.
This pattern has a con, that it uses the "this"
keyword, which will be tricky some times because the context of this changes accordingly.
Lets see one practical example and also workaround to resolve the issue:
Problem:
function Triangle(a,b,c){
this.a=a;
this.b=b;
this.c=c;
}
Triangle.prototype=function(){
var perimeter=function(){
return this.a+this.b+this.c;
}
var area = function(){
var s=(perimeter())/2;
return Math.sqrt(s*(s-this.a)*(s-this.b)*(s-this.c));
};
return {
area:area
}
}();
var triangle1=new Triangle(10,10,10);
alert(triangle1.area());
}
This won't work as you were calling perimeter function from area method , so context of this changes to the caller function area , so area doesn't have a,b,c so this.a,this.b,this.c in perimeter functions gets undefined. So the work around is pass this to the perimter function and use it.
Solution:
function Triangle(a,b,c){
this.a=a;
this.b=b;
this.c=c;
}
Triangle.prototype=function(){
var perimeter=function(thisob){
return thisob.a+thisob.b+thisob.c;
}
var area = function(){
var s=(perimeter(this))/2;
return Math.sqrt(s*(s-this.a)*(s-this.b)*(s-this.c));
};
return {
area:area
}
}();
var triangle1=new Triangle(10,10,10);
alert(triangle1.area());
}
This will popup the area of triangle given three sides of triangle ie.. 43.30127018922193
.
Thanks for reading :)