JavaScript Prototype Chaining
As we know there are no classes in JavaScript but we can acheive this by constructor function. Now question comes how can we achieve Encapsulation ? Yes we can do it as object can encapsulate data and it it’s behavior (methods). Aggregation? Sure, an object can contain other objects. Actually, this is almost always the case as methods are functions, and functions are also objects. So we can acheive inheritance in JavaScript also.
What is Prototype Chaining ?
Every function has a prototype property, which contains an object. When a function is invoked by new keyword then it creates a new instance/object which has a secret link to prototype object. The secret link (called proto) allows methods and properties of prototype as they are belongs to the newly created object. It plays a major role for implementing inheritance in JavaScript.
Let’s take an example, basic example of inheritance means classes can be Shape, TwoDShape, ThreeDShape. Here Shape is the base class and TwoDhape inherited from Shape and ThreeDShape inherited from TwoDShape. before we move in more details please have a look into the below snippet.
function Shape(){
this.name = "Shape";
this.toString = function(){
return this.name;
}
}
function TwoDShape(){
this.name = "2D Shape"
}
function ThreeDShape(side, height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return 0.5 * this.side * this.height;
}
}
//These codes will do the magic for the inheritance
TwoDShape.prototype = new Shape();
ThreeDShape.prototype = new TwoDShape();
Code language: JavaScript (javascript)
Now see what we did. We created Three functions named Shape, TwoDShape and ThreeDShape. The Shape has one property called “name” and one method called “toString”. The toString() will return the name property of the object. The TwoDShape has one property called “name”. The ThreeDShape has three property called “name”, “side”, “height” and one method getArea().
Now see what happening next. Here we are overrided the prototype property of TwoDShape completely with another new object by invoking the Spape() with new keyword instead of augmenting each indivisual property. If we completely override prototype object it will create a side effect on the constructor property of prototype. How can we overcome from this ?. See the following snippets by adding the constructor property.
TwoDShape.prototype.constructor = TwoDShape;
ThreeDShape.prototype.constructor = ThreeDShape;
Code language: JavaScript (javascript)
Lets do substantiation and check the instance properties. the getArea() will return the area of the triangle.
var triangle = new ThreeDShape(30, 20);
triangle instanceof Shape; // true
triangle instanceof TwoDShape; // true
triangle instanceof ThreeDShape; // true
triangle instanceof Array; // false
triangle.constructor; //ThreeDShape()
triangle.getArea(); //300
triangle.toString(); // Triangle
Code language: JavaScript (javascript)
We can validate the instanceof property. But the toString() is not defined in the ThreeDShape. So how it is returning “Triangle” ?. Now see some interesting notes on JavaScript execution on the call of toString();
- It loops through all the properties of “triangle” and does not found the method called toString().
- It looks at the triangle.proto points to; This object is the instance of new TwoDShape created during the inheritance process.
- Now JavaScript engine loops through thr instance of TwoDShape and does not found toString(). Again it checks proto of this object, This time proto points to the instance of Shape created by using new operator.
- Now JavaScript engine will examine new Shape() and will found the toString().
- Now this method called toString() wil be invoked in the context of “triangle”. i.e. this object will point to the triangle instance.
Searching of a property/method in the context of the object. if property not found it will search in the prototype, still not found it will search next parent prototype like this until it found the property or reaches to the prototype of Object class. This linking/chaining is called prototype chaining.
Move the shared properties to prototype.
When we are creating instances by using constructor function, then own properties are added using “this”. but this not a good approach for the properties which won’t change across instances. let’s take the previous example when we create object using new Shape(), a new property named “name” will be created and assigned to the this context and stored somewhere in the memory. We can observe that the value of “name” property will not change across the instances, so it is better to attach the name property to the prototype and it will be shared among all the instances. Let’s have a look, we will rewrite the Shape().
function Shape(){};
Shape.prototype.name = "Shape";
Code language: JavaScript (javascript)
Now every time you create instance of new Shape(), this object will not have the “name” property, but it can use the one added to the prototype. This is more efficient and will not take more space in memory, but this can only be used for the properties which won’t change across instances, Methods are ideal for this type of sharing. Let’s rewrite the entire code once again Shape, TwoDShape, ThreeDShape.
//Rewrite of Shape
function Shape(){};
Shape.prototype.name = "Shape";
Shape.prototype.toString = function(){
return this.name;
}
//Rewrite of TwoDShape
function TwoDShape(){};
TwoDShape.prototype = new Shape();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = "2D Shape";
//Rewrite the ThreeDShape
function ThreeDShape(side, height){
this.side = side;
this.height = height;
}
ThreeDShape.prototype = new TwoDShape();
ThreeDShape.prototype.constructor = ThreeDShape;
ThreeDShape.prototype.name = "Triangle";
THreeDShape.prototype.getArea = function(){
return 0.5 * this.side * this.height;
}
Code language: JavaScript (javascript)