5. Prototype based inheritance in JavaScript
As we have seen in our last post the association of prototype
and __proto__
objects creates the interesting phenomenon of prototype based inheritance in JavaScript. EcmaScript has specified some rules for controlling the behavior of [[prototype]]
object.They are:
All objects have an internal property called
[[prototype]]
whose value is eithernull
or an object and it will be used for implementing inheritance.Every prototype chain must have a finite length, i.e. if we recursively start accessing the
[[prototype]]
object of an object, this internal property must eventually terminate atnull
.
So how does the inheritance look like? Let's visualize it: For this let us take the stages of human evolution as our inheritance chain:
Australopithecus afarensis->Homo habilis->Homo erectus->Homo neanderthalensis->Homo sapiens
The inheritance would look something like this:
So here:
All constructor function’s
__proto__
property points to theprototype
property of the JavaScriptFunction
constructor functionAll constructor function’s
prototype
property points to an objectThe
constructor
property of theprototype
object of a constructor function points to the constructor function itselfThe
__proto__
property of theprototype
object of a constructor function points either to it’s parent constructor function’sprototype
property or toprototype
property of the JavaScriptObject
constructor function. And this is the mechanism used in JavaScript to inherit methods of its parent objects. Ultimately the__proto__
property of theObject
constructor function points tonull
So why is this mechanism so powerful? Let's see some code We define a constructor function and create 2 object using it
function MyPseudoClass(welcomeString){
this.welcomeString = welcomeString;
}
var myPseudoClassObject1 = new MyPseudoClass("My precious");
var myPseudoClassObject2 = new MyPseudoClass("gollum gollum!");
console.log(myPseudoClassObject1);//Output MyPseudoClass { welcomeString: 'My precious' }
console.log(myPseudoClassObject2);//Output MyPseudoClass { welcomeString: 'gollum gollum!' }
So each of the objects created contains it's own copy of the property welcomeString
. Let us assume we want the objects of MyPseudoClass
to have the ability to print the welcomeString
property. One way of doing it is adding a method to MyPseudoClass
. Let's see how the objects would look then.
function MyPseudoClass(welcomeString){
this.welcomeString = welcomeString;
this.printWelcomeString = function(){
console.log(this.welcomeString);
};
}
var myPseudoClassObject1 = new MyPseudoClass("My precious");
var myPseudoClassObject2 = new MyPseudoClass("gollum gollum!");
myPseudoClassObject1.printWelcomeString();// Output My precious
myPseudoClassObject2.printWelcomeString();//Output gollum gollum!
console.log(myPseudoClassObject1);//Output MyPseudoClass { welcomeString: 'My precious', printWelcomeString: [Function] }
console.log(myPseudoClassObject2);//Output MyPseudoClass { welcomeString: 'gollum gollum!', printWelcomeString: [Function] }
So each object has its own copy of printWelcomeString
method. In real life scenarios this will cause repetition of the method in all the object instances even though it's definition is the same for all the objects unlike the welcomeString
property which varies for each object. So this will lead to high memory consumption. Also any object inheriting from(i.e. pointing to the prototype
property of the MyPseudoClass
constructor function will not have access to the printWelcomeString
method. How to avoid this? Simply move the printWelcomeString
property from the constructor function to it's prototype
object. Now we see the objects created don't have the method in them but still can access it as it's available to them in their prototype chain(i.e. myPseudoClassObject1.__proto__.printWelcomeString()
)
function MyPseudoClass(welcomeString){
this.welcomeString = welcomeString;
}
var myPseudoClassObject1 = new MyPseudoClass("My precious");
var myPseudoClassObject2 = new MyPseudoClass("gollum gollum!");
MyPseudoClass.prototype.printWelcomeString = function(){
console.log(this.welcomeString);
};
myPseudoClassObject1.printWelcomeString();// Output My precious
myPseudoClassObject2.printWelcomeString();//Output gollum gollum!
console.log(myPseudoClassObject1);//Output MyPseudoClass { welcomeString: 'My precious'}
console.log(myPseudoClassObject2);//Output MyPseudoClass { welcomeString: 'gollum gollum!' }
console.log(MyPseudoClass.prototype)//Output MyPseudoClass { printWelcomeString: [Function] }
Now let's create a child class and make it inherit the MyPseudoClass
.
function MyPseudoClass(welcomeString){
this.welcomeString = welcomeString;
}
console.log(MyPseudoClass.prototype); //Output MyPseudoClass {}
function MyPseudoChildClass(hobbitName){
this.hobbitName = hobbitName;
}
MyPseudoChildClass.prototype.__proto__ = MyPseudoClass.prototype;
console.log(MyPseudoChildClass.prototype); //Output MyPseudoChildClass {}
console.log(MyPseudoChildClass.prototype.__proto__); //Output MyPseudoClass { printWelcomeString: [Function] }
var myPseudoChildClassObject = new MyPseudoChildClass("Frodo");
console.log(myPseudoChildClassObject) //Output MyPseudoChildClass { hobbitName: 'Frodo' }
console.log(myPseudoChildClassObject.__proto__) //Output MyPseudoChildClass {}
console.log(myPseudoChildClassObject.__proto__.__proto__)// Output MyPseudoClass { printWelcomeString: [Function] }
console.log(myPseudoChildClassObject.__proto__.__proto__.__proto__) // Output {}
console.log(myPseudoChildClassObject.__proto__.__proto__.__proto__.__proto__) //Output null
So, this proves what we just understood about the JavaScript prototype chain:
MyPseudoChildClass.prototype.__proto__ = MyPseudoClass.prototype;
This is where the actual inheritance happens and a link is added the existing prototype chain of Object->MyPseudoClass.
myPseudoChildClassObject.__proto__
points to theprototype
property ofMyPseudoChildClass
.myPseudoChildClassObject.__proto__ === MyPseudoChildClass.prototype
is truemyPseudoChildClassObject.__proto__.__proto__
points toportotype
property ofMyPseudoClass
. myPseudoChildClassObject.proto.proto === MyPseudoClass.prototype` is true.myPseudoChildClassObject.__proto__.__proto__.__proto__
points to theprototype
property ofObject
constructor function.myPseudoChildClassObject.__proto__.__proto__.__proto__ === Object.prototype
is truemyPseudoChildClassObject.__proto__.__proto__.__proto__ === Object.prototype
points to the__proto__
property of prototype of theObject
constructor function which is alwaysnull
.myPseudoChildClassObject.__proto__.__proto__.__proto__.__proto__===Object.prototype.__proto__
is true.
So this sums up the basics of how prototype based inheritance is achieved in JavaScript.
In the next post we will learn about accessing the prototype chain, the remaining method of object creation Object.create
and then compare the important difference between the various methods of object creation.
References
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Using_prototypes_in_JavaScript https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function