Prototypal Inheritance and Classes in JavaScript
Hello everyone, in this article, we would be discussing:
These are very important concepts and would help you to understand Object-Oriented Programming(OOP) in JS. So, keep reading till the end and I hope you'll learn something from it.
Objects
We had discussed earlier how to create and work with objects using literal notation here.
const phone = {
RAM: "8GB",
OS: "Andriod"
}
In practice, we often need to create many similar objects like a list of phones, employees, etc. So, we can achieve this by creating custom types in JS using Constructor Function and then creating multiple objects from it. In other programming languages, we generally use class to define this kind of custom type but in JS, the class system is built directly using functions.
So, rather than using classes in JS directly, we can learn how to do the same using constructor functions which is the base of Object-Oriented Programming in JS.
ES6 introduces the class keyword that allows us to define custom types. But, classes in JS are syntactic sugar over constructor functions with some enhancements.
Constructor Functions
Constructor functions are like regular functions with some conventions:
function Employee(name){
this.name = name;
this.role = "Developer";
}
And, this is to create an object using that constructor function.
const employee = new Employee("Souvik");
console.log(employee); // Employee {name: "Souvik", role: "Developer"}
this inside the function definition points to the object that has been created using the new keyword in front of the constructor function while invoking it.
So, what if we don’t use the new keyword while calling the function?
In that case, the function would be invoked as a regular function, a new object would NOT be created and returned. Let’s understand this part by invoking the function mentioned above without the new operator:
const employee = Employee();
console.log(employee); // undefined
As you can see, undefined would be returned which any regular function returns by default. Also, this would refer to global object window as the constructor function has been invoked as a regular function.
These are the followings the new keyword is responsible for while invoking constructor function:
`this` keyword in JS
We had talked about this keyword before and found out this behaves differently based on implementation. There’re 4 ways to call a function and this refers to a different object in each case.
Prototypal Inheritance
The problem with the constructor function is that if there’s any method present in the constructor function then that will be created for every instance created using the constructor function.
function Employee(name){
this.name = name;
this.role = "Developer";
this.printDetails = function (){
console.log(`${this.name} works as a ${this.role}`)
}
}
So, to make things memory efficient, we can add methods to the prototype property of the constructor function, so that all instances of a constructor function can share the same methods.
function Employee(name){
this.name = name;
this.role = "Developer";
}
Employee.prototype.printDetails = function (){
console.log(`${this.name} works as a ${this.role}`)
}
const employee = new Employee("Souvik");
employee.printDetails(); // Souvik works as a Developer
So, what is a prototype?
A prototype is just an object and all objects created from a constructor function are secretly linked to the prototype.
The prototype also keeps a reference to its own prototype object. And, prototype’s prototype is also linked to its own prototype and so on. This is how it forms prototype chain.
JavaScript uses this link between an object and its prototype to implement inheritance which is known as Prototypal Inheritance.
When we try to access a property or method of an object,
But, one question still arises, how is an object created by a constructor function secretly linked to its prototype?
The answer is any object created by a constructor function is linked to its prototype using the __proto__ property which is made by the constructor function and directly refers to the constructor function's prototype.
console.log(employee.__proto__ === Employee.prototype); // true
__proto__ has been used here just for learning purposes. Don't use or reassign the __proto__ property anywhere in production code as it's not supported by all browsers and updating its value might create performance issues since JS searches for properties along the prototype chain.
If we need to check the prototype for an object, we can use the Object.getPrototypeOf() method for the same which takes an object as an argument and returns the prototype of that object.
console.log(Employee.prototype === Object.getPrototypeOf(employee)); //true
Object.create()
As we discussed, using the __proto__ property is not a good practice to use in code, so the same shouldn't be used to implement inheritance or build a prototype chain.
That's why ES5 introduced Object.create() method to implement prototypal inheritance.
Object.create() takes an object as an argument and returns a new object with its __proto__ set to the object that was passed as argument into Object.create().
const person = {
name: "Souvik",
greet: function(){
console.log(`Hi, I’m ${this.name}`);
}
}
const teacher = Object.create(person);
teacher.teach = function (subject) {
console.log(`I can teach ${subject}`);
}
teacher.greet(); // Hi, I'm Souvik
teacher.teach("JavaScript"); // I can teach JavaScript
console.log(Object.getPrototypeOf(teacher) === person); // true
We can leverage Object.create() the following way to implement inheritance.
function Animal(name){
this.name = name;
}
Animal.prototype.walk = function (){
console.log(`${this.name} can walk`);
}
function Dog(name, lifetime){
Animal.call(this, name); // calling parent constructor function to initialize parent properties for child objects
this.lives = lifetime;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.details = function(){
console.log(`${this.name} can live for ~${this.lives} years`);
}
const dog = new Dog("Dobby", 10);
dog.walk(); // Dobby can walk
dog.details(); // Dobby can live for ~10 years
In this way Dog inherits properties and methods from Animal using prototypal inheritance. But this is a bit tricky and verbose.
That's why ES6 introduces the class and extends keyword to simplify inheritance implementation in JS. Classes in JS are special functions. And the same implementation using class would look like this:
class Animal{
constructor(name){
this.name = name;
}
walk(){
console.log(`${this.name} walks`);
}
}
class Dog extends Animal{
constructor(name, lifetime){
super(name);
this.lives = lifetime;
}
details(){
console.log(`${this.name} can live for ~${this.lives} years`);
}
}
const dog = new Dog("Dobby", 10);
dog.walk(); // Dobby can walk
dog.details(); // Dobby can live for ~10 years
console.log(typeof Animal); // function
That's all. Thanks for reading till now.
Please share this article with your network if you found it useful and feel free to comment if you've any doubts about the topic.