Demystifying Prototypal inheritance in JavaScript

Demystifying Prototypal inheritance in JavaScript

Introduction

We all write this code quite often when working with arrays.

var arr = [1,2,3];
arr.push(50);
//[1,2,3,50]

But where did this .push() come from or for a matter of fact where do .map() .filter() .reduce() actually come from. As we all know (almost) everything in JavaScript is an object, similarly the above array is also an object, but we never defined these methods on our array, so how are we able to use them? The answer is Inheritance, a special kind of inheritance.

As we all know inheritance is just a way of reusing behaviour (properties and methods), from a different Class so we don't have to write same code again and again.

Well the thing about JavaScript is unlike C++ and Java it doesn't has the concept of Classes. Surely we do have the class keyword in JavaScript but that's just syntatic sugar (This can be a blogpost for another day). But believe me there is nothing like Classes in JavaScript.

So the question arises how do we reuse some properties and methods from another object. The answer is Prototypal Inheritance.

A quick google search of prototypal inheritance lands us with this definitiion

Prototype-based programming is a style of object-oriented programming in which behaviour reuse is performed via a process of reusing existing objects that serve as prototypes.

Thus if an objectA inherits properties and methods from objectB , objectB is the prototype of objectA.

But how does prototypal inheritance works?

To understand this we need to understand two things __proto__ and prototype.

[[Prototype]] / __proto__

Every object in JavaScript has an hidden property : [[Prototype]] , which is added implicitly by JavaScript to it. It is either a reference (linkage) to the prototype of the object or a null value. But we can't access this directly thus to see this link we use the __proto__ getter and setter method.

Example:

Screenshot (884).png

As we can see __proto__ helps us see the refrence to the Array constructor function's prototype object, and it is from this constructor function that we have access to all the methods like .push() .map() and so on.

Do check it out in your own console.

Now what is this prototype object?

prototype

Every Constructor function in JavaScript has a prototype object that contain's all these additional methods and properties that could be reused by it's instances (i.e objects created from that constructor function).

Thus whenever a new object is created from the Constructor function, JavaScript automatically assigns the __proto__ property of that new object a reference to the prototype object of the Constructor function.

Screenshot (886).png

We can see the prototype object in the image above for the Array Constructor function.

Our arr.__proto__ refers to the above prototype object. We can verify this by comparing the two.

Screenshot (888).png

Prototype Chain

So when we access .push() method on our array, JavaScript checks for it first of all in it's own properties, and when it can't find it in it's own properties it looks for the method in the __proto__ link and finds it in the prototype object of Array constructor function, this linkage is known as prototype chain.

But what happens when we use arr.toString() method. It definitely is not present in the Array Constructor, so JavaScript again looks up the __proto__ link of the Array Constructor function and finds it in the prototype object of the Object constructor function.

We can see that .toString() is present in the Object Constructor's prototype object, in the image below.

Screenshot (890).png

Similarly JavaScript keeps on looking for a property or method up the prototype chain, until it can find it or reaches the end of prototype chain, which is a null value.

Screenshot (895).png

We can see in the image above that , if we keep on moving up the prototype chain, eventually we reach a null value.

Implementing prototypal inheritance

Now that we understand how prototypal inheritance works in JavaScript, let's see how we can implement prototypal inheritance for our custom objects.

Let's take an example of two objects:

const human = { eat:true, sleep:true , walk:true };

const superhuman = { canFly:true };

A superhuman is also a human, thus he needs to also have eat, sleep and walk properties, let's try inheriting these by modifying our prototype chain.

Non-safe way:

superhuman.__proto__ = human;

superhuman.eat  //true

superhuman.__proto__ === human //true

In the above example we manually set the human as prototype of superhuman , by manually setting the __proto__ of superhuman. Thus now we can access all the properties of human in superhuman.

Note : While this is a completely valid way of changing the prototype chain, It is not recommended as it messes up with our JavaScript compiler's optimisations.

ES5 way:

We can also inherit the properties of an Object by using the Object.create() method.

const human = { eat:true, sleep:true , walk:true };
const superhuman = Object.create(human ,{canFly: {value:true}});

superhuman.eat //true

superhuman.__proto__ === human //true

ES6 way:

The modern and the most commonly used way is using the class and the extends keyword, which makes it look similar to other OOP languages and is implemented in a similar way.

class Human{
   constructor(){
      this.eat=true,
      this.sleep=true,
      this.walk=true
    }    
}

class Superhuman extends Human {
    constructor(){
        super();
        this.canFly=true
    }
}

const superhuman = new Superhuman();

superhuman.eat //true

superhuman.__proto__ === Superhuman.prototype //true

Conclusion

Hopefully now you finally understand what does __proto__ , prototype and prototypal inheritance mean in JavaScript.

But some of you still might have one doubt : primitive types.

How are we able to access these methods on primitives?

const x = 2;
x.toString() // '2'

Well to put simply whenever we call a method like .toString() on primitives, Javascript implicitly converts it into an object and then looks up the prototype chain for that property/method, in our case the .toString() method.

This finally concludes our learning of the prototypal inheritance 😅. This was a long read, hopefully now you understand prototypal inheritance better. Please do leave your feedback in the comments below.