Prototypal Inheritance and Classes in JavaScript

Prototypal Inheritance and Classes in JavaScript

Hello everyone, in this article, we would be discussing:

  • Constructor function,
  • Prototypes,
  • Inheritance, classes in JS

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:

  • These functions should be invoked with the?new?operator.
  • Naming of these functions are written in CamelCase(starting with capital letter e.g. Employee) by convention
  • These functions should not have an explicit return value


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:

  • Create a new object and assign it to?this
  • Add properties to the object with the given value
  • Return the newly created object

`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.

  • If calling a constructor function, then?this?sets to the newly created object
  • Invoking a function that belongs to an object would set?this?to the object itself, which is called?Implicit binding.
  • Simply invoking a regular function would set?this?to the global object?window.
  • The last way of invoking a function allows us to set?this?ourselves using?call(),?apply()?and?bind()?methods - that’s known as?Explicit binding, talked about it?here?earlier as well.

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,

  • it tries to find that in the object’s own properties. Any properties or methods defined in the object itself get the highest precedence over defining the same elsewhere just like variable shadowing in the scope chain discussed here.
  • If it doesn’t get that within the object’s properties then it will try to find that in the object’s constructor’s prototype.
  • If it’s not there even in the prototype object, the JavaScript engine will continue looking up the prototype chain to get the value. At the end of the chain, there’s?Object()?object, the top-level parent - if the property is not found even there, then the property is?undefined.

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.

要查看或添加评论,请登录

Souvik Jana的更多文章

  • JS Objects in depth

    JS Objects in depth

    Hello everyone, we would be covering all about JS objects today which would help you to make better use of objects…

    4 条评论
  • Call, apply and bind in JS

    Call, apply and bind in JS

    Hello everyone, in this article, we would be discussing: Implicit binding, Explicit binding in JavaScript the call…

  • All about Functions and Scopes in JavaScript

    All about Functions and Scopes in JavaScript

    Hello everyone, we would be covering all about JS functions, callbacks, scopes, closures in-depth here which would help…

    2 条评论

社区洞察

其他会员也浏览了