The Javascript uses the Prototypes to implement the inheritance. In this tutorial, we will learn how to use Prototype to create Inheritance. Inheritance makes code reusable, readable & maintainable by sharing the instance of existing properties & methods across objects
Table of Contents
What is Prototype Inheritance
Inheritance is a mechanism by which one object acquires the property and behavior of another object. In classical languages like C# & Java, this is done with classes.
But in Javascript, we do not have the concept of classes. JavasScript has only Objects. To implement inheritance JavaScript makes use of Prototype. The Inheritance is known as Prototype Inheritance.
Prototype Recap
A Prototype is just another JavaScript Object. The JavaScript assigns it to the [[Prototype]]
property of the object when it creates it. The [[Prototype]]
is the internal property and is hidden from us. This Property points to the Prototype Object or NULL.
Accessing the Prototype of an object
We cannot access the [[Prototype]]
directly. But we can access it either by using the __proto__
property or using the getPrototypeOf
method of the Object
The Prototype Chain
Prototype objects are just like any other Javascript object. Hence Even they also have prototype objects. We can traverse using an object’s prototype until we find null
. This is referred to as prototype chain
The image below shows a Prototype chain in JavaScript.
At the bottom, we have John
object, which has a [[Prototype]]
property. It points to Foo
object. The Foo
object is the prototype of the John
object.
The Prototype object can also have a Prototype Object. Boo
is a Prototype of Foo
. The Prototype chain usually ends with Object.prototype
. The Prototype of Object.prototype
is NULL.
Need for Inheritance
First, let us understand why we need Inheritance in the first place.
Take a look at the following snippet. The constructor function Person
defines two properties and one method sayHi
.
1 2 3 4 5 6 7 8 9 10 | var Person = function(firstName, lastName) { this.firstName=firstName this.lastName=lastName this.sayHi=function() { console.log(" Hello "+this.firstName+' '+this.lastName) } } |
We create two new objects (john
& bill
) from the Person
function.
1 2 3 4 5 6 7 8 | var john = new Person("John","Dyer") john.sayHi(); //Hello John Dyer var bill= new Person("Bill","Gates") bill.sayHi(); //Hello Bill Gates |
The code above works perfectly well. But we can make it better
The issue is with the sayHi
method. Every time we create a new Person it also creates an instance of sayHi
method in the Memory.
If you happen to create many such Person objects, then it will also mean that you have many instances of sayHi
occupying the memory.
The solution is to create a single instance of sayHi
method and share it among all the objects created from the Person function.
This is where we make use of Prototype Inheritance.
Prototype Inheritance
Whenever we create a function, JavaScript adds an extra property prototype
to the function. The prototype
property is an object. We can refer to it using the <functionName>.prototype
. In the case of Person function, it becomes Person.prototype
.
Whenever we create a new object using a new operator from the Person Function, the Person.prototype
becomes the Prototype of the newly created object.
In the code from the previous example, we make a small change. We remove the sayHi
function from the Person
function. We add it to the Person.prototype
object.
Run the code, and you will see the same result as the previous one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var Person = function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; Person.prototype.sayHi = function () { console.log(" Hello " + this.firstName + " " + this.lastName); }; var john = new Person("John","Dyer") console.log(john) john.sayHi(); var bill= new Person("Bill","Gates") console.log(bill) bill.sayHi(); |
The sayHi
method no longer belongs to the john
or bill
objects.
But you will find them in the __proto__
property of both john
& bill
objects. Both these methods actually point to the Person.prototype
object. You can use the ===
to check if they are equal.
1 2 3 4 5 6 7 | console.log(john.__proto__ === Person.prototype) //true console.log(bill.__proto__ === Person.prototype) //true |
We have only one instance of Person.prototype
object in the memory. The prototype Property of the function point to it. The [[Prototype]] property of the john
& bill
objects also point to it.
But how did john.sayHi()
& bill.sayHi()
work if they did not contain the sayHi
method.
How Prototype Inheritance Works
When we ask for a property or method from an object, JavaScript tries to read the property from the object itself. If the object lacks the property, then JavaScript looks for the property in the prototype object. And if that prototype object also does not contain the property, then JavaScript looks in its prototype. This process continues until it reaches the Object.prototype
. The prototype of Object.prototype
is NULL. Hence the search stops there and returns the undefined value.
When we call john.sayHi()
, The javascript searches for sayHi
method in the john
object. It does not find it there. So it looks for it in the __proto__
of john. The __proto__
of john
points to Person.prototype
. It finds the method there, hence executes it, and returns the result.
Inheritance & Built-in Objects
JavaScript has several built-in or native Objects. The following tables show the list of built-in objects, with their corresponding prototypes.
Built in Object | Corresponding Prototype |
---|---|
Object | Object.prototype |
Array | Array.prototype |
Function | Function.prototype |
String | String.prototype |
Number | Number.prototype |
Date | Date.prototype |
Boolean | Boolean.prototype |
BigInt | BigInt.prototype |
Symbol | Symbol.prototype |
Object.prototype
The Object()
is a built-in constructor function. We can create new objects using it. All the objects that we create from it get the Object.prototype
as their prototype
1 2 3 | console.log(Object.prototype) |
As you can see from the image, the Object.prototype
defines few methods. The constructor property points to Object()
Constructor function. Because Object.prototype
is created from it.
There are properties like hasOwnProperty
, IsPrototypeOf
, propertyIsEnumerable
, toLocalString
, toString
, valueOf
, etc. Every object that inherits from the Object.prototype
have access to these properties.
The following example creates obj
object using the Object
constructor. It can access the hasOwnProperty
because Object.prototype
is its prototype
1 2 3 4 5 6 7 8 | obj = new Object(); obj.name="Roger Mayer" console.log(obj.hasOwnProperty("name")) //true |
You can verify it by creating the new object with null
as its prototype as shown in the example below. Accessing the hasOwnProperty
results in TypeError
, becuase it is not present in its prototype chain.
1 2 3 4 5 6 7 8 | obj2 = Object.create(null); obj2.name="Roger Mayer" console.log(obj2.hasOwnProperty("name")) //Uncaught TypeError: obj2.hasOwnProperty is not a function |
String.prototype
Similarly, when you create a new String
object, the newly created objects will get String.prototype
as their prototypes.
As you can see from the image, the String.prototype
has many methods. Due to Prototype Inheritance, the newly created objects will have access to all these methods.
The prototype of String.prototype
is Object.prototype
. Hence the newly created object can also access the methods of Object.prototype
1 2 3 4 5 6 7 8 9 | var str = new String( "This is string" ); //str.__proto__ == String.prototype //str.__proto__.__proto__ == Object.prototype //str => String.prototype --> Object.prototype --> Null |
Simple primitive types
Note that JavaScript has seven simple primitive types. They are string, boolean, number, BigInt, Symbol, null, and undefined. They are not objects.
But JavaScript creates an object wrapper around the primitive types. This enables us to use prototype methods of the String object on the primitive string.
1 2 3 4 5 6 7 8 | let primString="hi" let objString=new String("Hi") console.log(primString) console.log(objString) |
A console.log of both will make the difference clear.
But comparing the __proto__
of both results in true. You can also access all the methods of the String.prototype
. When we call a property or method on a string primitive, JavaScript automatically coerces it to a String object. Hence it works.
1 2 3 4 5 6 7 8 | console.log(primString.__proto__==objString.__proto__) //true //All methods of String.prototype still work on primitive strings. primString.slice(0,1) // |
Array.prototype
Similarly, Arrays inherit from Array.prototype
1 2 3 4 5 6 7 8 9 | var arr = ['Ford', 'Chevrolet', 'Buick', 'Tesla']; //arr.__proto__ == Array.prototype //arr.__proto__.__proto__ == Object.prototype //arr => Array.prototype --> Object.prototype --> Null |
Creating objects with Prototype
Apart from the Object constructor function, there are other ways in which you can create an Object.
Objects Created with Literal Syntax
The following example creates person
object using the object literal syntax. The objects created using the literal syntax gets the Object.prototype
as its [[Prototype]]
1 2 3 4 5 6 | var person = { name: "Bill Gates"}; //prototype chain //person --> Object.prototype --> null |
Objects from constructor functions
We looked at this in the above examples.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var Circle = function(radius) { this.radius=radius } Circle .prototype = { area: function () { return Math.PI * this.radius * this.radius; }, circumference : function() { return 2 * Math.PI * this.radius }, } var cir = new Circle (10) //cir.__proto__ == Circle .prototype //cir.__proto__.__proto__ == Object.prototype //cir => Circle.prototype --> Object.prototype --> Null |
With Object.create
Using Object.create
method allows us to specify the prototype of the newly created object.
For Example, let us create an object obj1
using the literal syntax
1 2 3 4 5 | var obj1 = {a: 1}; // // obj1 ---> Object.prototype ---> null |
In the following example, we use Object.create
to create obj2
using the obj1
as prototype.
1 2 3 4 5 | var obj2 = Object.create(obj1); // obj2 ---> obj1 ---> Object.prototype ---> null |
You can access the property of the obj1
from obj2
because it is its prototype.
1 2 3 4 5 | console.log(obj2.a); // 1 inheritated from obj1 |
You can create an object without a prototype by Passing the Null as the argument.
1 2 3 | Object.create(null). |
Adding Property to Prototype
We can add or alter the Property or method of a Prototype object. These changes will immediately become visible in all of the objects that are based on that prototype
In the example below, we change the sayHi method midway through the program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | var Person = function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; Person.prototype.sayHi = function () { console.log(" Hello " + this.firstName + " " + this.lastName); }; var john = new Person("John","Dyer") john.sayHi(); //Hello John Dyer var bill= new Person("Bill","Gates") bill.sayHi(); //Hello Bill Gates console.log("Change the method sayHi") //We change the sayHI method here //Return lastName+FirstName Person.prototype.sayHi = function () { console.log(" Hello " + this.lastName + " " + this.firstName); }; //Now both prints lastName+FirstName john.sayHi(); //Hello Dyer John bill.sayHi(); //Hello Gates Bill |
Writing doesn’t use prototype
The Prototype inheritance works only for reading the Properties. Write/delete operations work directly with the object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var Person = function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; Person.prototype.sayBye="Bye"; var john = new Person("John","Dyer") var bill= new Person("Bill","Gates") console.log(john.sayBye); //Bye console.log(bill.sayBye); //Bye //Writing Property //This will create a new Property in the john object //does not change the Person.prototype.sayBye john.sayBye="Goodbye" console.log(john.sayBye); //Goodbye console.log(bill.sayBye); //Bye |
Delete also do not use prototype
Similarly, the delete operator deletes the property from the object. But it will not traverse the prototype chain to delete the objects from the prototype
In the example, we delete the sayBye
from john. A similar property exists in Person.prototype
. It will not affect it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | var Person = function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; Person.prototype.sayBye="Bye"; var john = new Person("John","Dyer") var bill= new Person("Bill","Gates") console.log(john.sayBye); //Bye console.log(bill.sayBye); //Bye john.sayBye="Goodbye" console.log(john.sayBye); //Goodbye console.log(bill.sayBye); //Bye //Deletes sayBye from john delete john.sayBye console.log(john.sayBye); //Bye console.log(bill.sayBye); //Bye //sayBye already deleted. Hence does nothing delete john.sayBye console.log(john.sayBye); //Bye console.log(bill.sayBye); //Bye |
You can directly delete it from the Person.prototype
. This will affect all the objects which have Person.prototype
as their prototype
1 2 3 4 5 6 | delete Person.prototype.sayBye console.log(john.sayBye); //Undefined console.log(bill.sayBye); //Undefined |