Before starting with prototypal inheritance
let’s first understand what a prototype is.
All the objects in JavaScript like
Array
,Boolean
,Date
etc all inherit properties and methods from their prototype.
Object
is the at top of the Prototype chain means all the other objects inherit their properties and methods from Object.prototype
.
Suppose we have Person
object constructor:
function Person(name, age) {
this.name = name;
this.age = age;
}
Then we create objects of Person
as follows:
const person1 = new Person("David", 30);
const person2 = new Person("John", 35);
console.log(person1); // {name: "David", age: 30}
console.log(person2); // {name: "John", age: 35}
If we want to add another property to the Person
object we can add that to an individual object like this:
person1.gender = 'Male';
but this will only add this property to person1 object.
console.log(person1.gender); // Male
console.log(person2.gender); // undefined
To add the property to the Person
object itself we have to add it to its prototype before creating objects so it will be available to all of its objects.
Person.prototype.gender = 'Male';
const person1 = new Person("David", 30);
const person2 = new Person("John", 35);
console.log(person1.gender); // Male
console.log(person2.gender); // Male
We can also add methods to the Person
prototype like this:
Person.prototype.display = function() {
console.log(this.name, this.age);
};
const person1 = new Person("David", 30);
const person2 = new Person("John", 35);
person1.display(); // David 30
person2.display(); // John 35
So to add any property or method to an object, we need to add it to its prototype.
If we compare the person1
and person2
display methods we will see that it returns true
console.log(person1.display === person2.display) // true
It returns true because in memory there is only a single copy of the display function because we added the method to its prototype so it's shared by all objects.
Let's see what happens when we add it directly inside the Person constructor
function Person(name, age) {
this.name = name;
this.age = age;
this.display = function() {
console.log(this.name, this.age);
};
}
const person1 = new Person("David", 30);
const person2 = new Person("John", 35);
person1.display(); // David 30
person2.display() // John 35
Now, let's compare the display methods:
console.log(person1.display === person2.display) // false
Now it displays false
because as we have added the method directly to the constructor function, each object created from Person
will have its own copy of function so there are two copies of display function in memory here and as we create more objects, the number of copies of display function inside memory will also increase.
So it's a bad practice and it's not recommended to create methods directly inside the constructor function instead we should add them to the prototype.
Now, we have some basic knowledge of prototypes, let's understand prototypal inheritance now.
Prototypal Inheritance
Let's start with the same Person
example above.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.display = function() {
console.log(this.name, this.age);
};
Now, let's create an Employee
constructor function that will inherit the properties and methods of the Person
.
function Employee(name, age, salary) {
Person.call(this, name, age);
this.salary = salary;
}
const emp = new Employee('Mike', 20, 4000);
Here, we are using Person.call
function to call the Person
’s constructor and pass the name
and age
to it.
Now, let’s check what emp
contains:
console.log(emp); // {name: "Mike", age: 20, salary: 4000}
As you can see, it contains all the properties of Person
plus its own properties.
Now, let's call the display method using the emp
object.
emp.display(); // Error: emp.display is not a function
Why do we get the error?
This is because we only mentioned inheriting properties of Person
using Person.call(this, name, age)
To inherit methods also, we need to link the prototype and then create an object.
Employee.prototype = Object.create(Person.prototype);
const emp = new Employee('Mike', 20, 4000);
emp.display(); // Mike 20
Now, let’s check the type of Employee
which we can check using constructor property
console.log(emp.constructor) // Person
console.log(Employee.prototype.constructor) // Person
It prints Person
which is wrong because we know that, it should be Employee
. The constructor property should always return the correct type.
Suppose, we have an array:
const numbers = [1, 2, 3, 4];
console.log(typeof numbers); // "object"
We get an object because every array in JavaScript is an object so to get its correct type we can use constructor
property that returns the correct type.
console.log(numbers.constructor) // Array
console.log(numbers.constructor === Array) // true
So to fix the issue with Employee
constructor we need to change its constructor type
Employee.prototype.constructor = Employee;
console.log(emp.constructor) // Employee
console.log(Employee.prototype.constructor) // Employee
Now, we get the correct result. The complete prototypal inheritance will look like this
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.display = function () {
console.log(this.name, this.age);
};
function Employee(name, age, salary) {
Person.call(this, name, age);
this.salary = salary;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
const emp = new Employee('Mike', 20, 4000);
console.log(emp); // { name: 'Mike', age: 20, salary: 4000 }
emp.display(); // Mike 20
console.log(emp.constructor); // Employee
As you can see, even for the simple prototypal inheritance we have to add a lot of extra code.
Therefore ES6 has added class syntax which allows us to implement the same in an easy way.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
display() {
console.log(this.name, this.age);
}
}
class Employee extends Person {
constructor(name, age, salary) {
super(name, age); // call super class constructor
this.salary = salary;
}
}
const emp = new Employee('Mike', 20, 4000);
console.log(emp); // { name: 'Mike', age: 20, salary: 4000 }
emp.display(); // Mike 20
console.log(emp.constructor); // Employee
As you can see, the class-based syntax is short and easy to understand when compared with prototypal inheritance.
Note: The class at the end uses the prototypal inheritance itself. It’s just that, we don’t need to worry about it
Thanks for reading!
Check out my recently published Mastering Redux course.
In this course, you will build 3 apps along with food ordering app and you'll learn:
- Basic and advanced Redux
- How to manage the complex state of array and objects
- How to use multiple reducers to manage complex redux state
- How to debug Redux application
- How to use Redux in React using react-redux library to make your app reactive.
- How to use redux-thunk library to handle async API calls and much more
and then finally we'll build a complete food ordering app from scratch with stripe integration for accepting payments and deploy it to the production.
Want to stay up to date with regular content regarding JavaScript, React, Node.js? Follow me on LinkedIn.
Comments (0)