JavaScript: An In-Depth Look at Prototypal Inheritance

An in-depth look at JavaScript inheritance and the concepts involved in implementing it with the help of code samples and console outputs.

Classes

JavaScript Inheritance is Hard

An in-depth look at JavaScript inheritance and the concepts involved in implementing it with the help of code samples and console outputs. It is aimed at people who have a basic understanding of how JavaScript works and are looking to understand JavaScript inheritance in a detailed manner. We’ll be restricting the scope to ECMAScript5, which is the version prior to ECMAScript6 (ES2015).

JavaScript Inheritance is Hard

People often argue that JavaScript’s inheritance is easier to understand compared to typical classical inheritance patterns found in other programming languages like Java because of the verbosity and complicated concepts like variable scopes (private, protected, friend, interface, etc.) found in these languages. Whilst such concepts don’t exist in JavaScript, it comes with its own set of features and concepts that aren’t always easy to understand, such as this, call, prototype, new, function constructors, and constructor, among other things.

Up till now, being a front-end developer, I have found that most of my work revolves around working with a JavaScript library/framework instead of native JavaScript. These libraries do an excellent job of abstracting away these language features and provide us with their own APIs, which leads to a faster application setup and makes writing application code both faster and easier. The downside to this, however, is that not working with native JavaScript can weaken our understanding of these concepts over time — everything gets a bit hazy. Of course we can build applications with just standard JavaScript APIs and many people prefer it. But a large part of the JavaScript community, especially those on the front end, usually work with a library. To become a better developer, it is essential that we have a good hold on these concepts. Today, we’ll aim to tighten that hold.

Classes

Unlike other programming languages, JavaScript doesn’t have any language construct called class.

Classes in JavaScript are implemented using functions. Whenever we use a function to create a class, it is called a function constructor. All objects are created using a function constructor and the new keyword.

ES2015, the new version of JavaScript, introduces the class keyword but it is merely syntactic sugar-coating over function constructors. ‘Function constructor’ and ‘class’ can be used interchangeably as they convey the same concept in JavaScript. We’ll see why such functions are called function constructors in a bit. Let’s create our first class:

function Person(name) {
this.name = name;
}var person = new Person('John');

While the syntax is very simple, there are several concepts in play here. Let’s try to understand them.

new, constructor

First, we create a function Person (it’s not a function constructor yet!). The first letter is capitalised merely as a convention to indicate that it is going to be used as a function constructor. Next, we use the new operator to create an instance of Person. Let’s see what new does under the hood:

  • It sets the value of this to the instance that is being created. Usually, this points to the object inside which the function is invoked. If newdidn’t perform this step, this would have pointed to the object in the context of which the statement var person = new Person('John')executes. You can read more about this here.
  • Returns this from the function implicitly, that is, we didn’t have to write return this; at the end of the function. Note that this implicit return only happens because we didn’t explicitly specify what to return.
  • Sets the constructor of person to Person. This is the step where Personbecomes the function constructor of person. If you log person.constructor you’ll see the following output:

Let’s dwell on the constructor keyword for a bit longer. In class-based languages, whenever we create a class blueprint, we specify the constructorfunction of it explicitly inside the class, and it is run every time a new object is created from the class. The job of the constructor function is to initialise the object that is being created.

So, how do we specify the constructor function of a JavaScript class? We don’t. In JavaScript, the constructor function of a class is the class itself. The function constructor is run every time a new object is created.

We can know the function constructor/class of any object by querying object.constructor.

Note that objects here are not to be interpreted as object literals — they can be a primitive data type too.

Prototypal Inheritance

prototype

Let’s check out the built-in function constructors of some common data types found in JavaScript to make it more clear:

Some of you may be wondering, “Hey, we didn’t use new here to create an instance. So, why was it required in the Person example and not here?” The reasoning behind this is that all of these instances are created using built-in function constructors. JavaScript provides us with simpler APIs to initialise such instances and doesn’t require us to use new to create them. But that doesn’t mean we can’t use new to create them:

Note: Use of new to create instances of built-in function constructors can lead to potential errors. For example, consider the following code:

In the above example, you would expect a and b to be equal but they are not. Why? Because:

new always creates an instance of object data type. The created instance, irrespective of whatever data type it holds, follows all the rules that a normal object would.

a and b are not equal because they are objects and two objects are not treated as equal by == or === unless they point to the same object in memory irrespective of whether they have the same value. This can seem counter-intuitive at first glance, especially in this example, since we would expect two variables storing the same number to be equal.

Prototypal Inheritance

JavaScript follows the prototypal inheritance pattern. Let’s understand it by going through the concepts involved one-by-one.

prototype

A common use case while creating classes is to add methods to it. Let’s see how we can do that:

function Person(name) {
this.name = name;
this.printName = function() {
console.log('Hi! My name is ' + this.name);
}
}var john = new Person('John');
john.printName(); // Hi! My name is John
var jane = new Person('Jane');
jane.printName(); // Hi! My name is Jane

The problem with this approach is that for every new Person instance, a new copy of printName is created which lives on the instance and wastes unnecessary memory. We don’t really need a new copy of printName every time an instance is created as it is constant and behaves the same across all instances. To fix this, we use the prototype keyword.

All functions in JavaScript have a prototype property by default, the value of which is an object. If a function is used as a function constructor, then all objects created using that function have access to the prototype object of that function.

function Person(name) {
this.name = name;
}Person.prototype.printName = function() {
console.log('Hi! My name is ' + this.name);
}var john = new Person('John');
john.printName(); // Hi! My name is John
var jane = new Person('Jane');
jane.printName(); // Hi! My name is Jane

Now, printName is only created once in memory while defining Person
and it lives on Person.prototype. Apart from saving memory, another big advantage of adding methods and variables to the prototype is that whenever we add anything to the prototype, it is instantly accessible by all created instances irrespective of when they were created. For example:

function Person(name) {
this.name = name;
}var person = new Person('John');Person.prototype.printName = function() {
console.log('Hi! My name is ' + this.name);
}person.printName(); // Hi! My name is John

If we had defined printName on Person directly without using prototype in the above example, then the person object wouldn’t have been able to access printName because person was instantiated before printName was added to Person.
Usually, we don’t really know all the methods/constants that we would need to add to a class at the time of defining the class. prototype
enables us to add stuff to a class at a later time and to make that stuff available across all instances—even if they were created in the past.

prototype chain

When we invoked person.printName , printName didn’t exist directly on person. So, how did JavaScript know that it would exist on Person.prototype? There must be something on person to tell it to use Person.prototype.printName. That something is the __proto__ property.

All objects in JavaScript have a __proto__ property called ‘dunder proto’ and often represented as [[Prototype]] which contains the reference to the prototype of the class from which the object was created.

In the above example, when we did person.printName what actually happened was that JavaScript first checked if printName existed on persondirectly. It didn’t, so person.__proto__ was used to access the prototype of Person where printName was found. Here’s what person.__proto__ looks like:

What if printName didn’t exist on the prototype of Person ? If this was the case, then Person.prototype.__proto__ would have been queried and so on. This sequential bottom-up lookup to evaluate a property on an object is called prototype chaining.

Remember when we did person.constructor in the section about the constructor keyword? Did constructor exist directly on person? The answer is no. person.constructor was actually a reference to Person.prototype.constructor. We already know that whenever a function is created it has a default prototype property which contains the prototype object. This prototype object also has its constructor property set to the function itself by default so that it can be used whenever we reference constructor on any object created using that function.

So, how high does this prototype chain go? All classes in JavaScript are inherited from the Object class. Whenever we look up a property on an object, Object.prototype is the extent to which the lookup process goes. In other words:

Object is the base class for all classes in Javascript and therefore, Object.prototype is the root of the prototypal chain.

If that property doesn’t exist even on Object.prototype, then it is deemed to not exist.

Creating Subclasses

call

Let’s check out a simple real-world example of prototype chaining which most of us would have used:

var arr = new Array(1, 2, 3);
arr.forEach(function(item) {
console.log(item);
});

When arr.forEach is executed, JavaScript first checks if forEach exists directly on arr. It doesn’t, so it checks arr.__proto__ internally, which points to Array.prototype since Array is the function constructor of arr. It finds forEach there and invokes it. toString is another example of a method that is found after traversing the prototypal chain. It lives on Object.prototype.

Note: The __proto__ property should not be overridden as it will break the prototype chain. It should also never be used to read values, as accessing it is very slow. You can read about it in detail here.

Creating Subclasses

A subclass is a class that inherits properties from another class but that also has its own unique properties that override the parent class’ properties or don’t exist on the parent class. Let’s create our first subclass:

// base class
function Loader(isLoading) {
this.isLoading = isLoading;
}Loader.prototype.showLoader = function() {
console.log('loader visible!');
}Loader.prototype.hideLoader = function() {
console.log('loader hidden');
}// subclass
function ProgressBarLoader(isLoading, loadedPercent) {
Loader.call(this, isLoading);
this.loadedPercent = loadedPercent || 0;
}ProgressBarLoader.prototype = Object.create(Loader.prototype);
ProgressBarLoader.prototype.constructor = ProgressBarLoader;ProgressBarLoader.prototype.showLoadedPercent = function() {
console.log('progress %: ', this.loadedPercent);
}ProgressBarLoader.prototype.setPercent = function(percent) {
this.loadedPercent = percent;
}var progressBarLoader = new ProgressBarLoader(false, 0);
progressBarLoader.showLoader(); // loader visible
progressBarLoader.hideLoader(); // loader hidden
progressBarLoader.showLoadedPercent(); // progress %: 0

Lots of things are going on here. Let’s examine them one by one:

call

call is a function that helps us invoke a method with arguments and set its this context to an explicitly specified object. This object is passed in as the first argument of call.

When we create an object of ProgressBarLoader, Loader is called in the context of ourProgressBarLoader object and the property isLoading is added to the object. In this way, the properties of Loader are added (alternatively, inherited, borrowed) to any instance created using ProgressBarLoader.
One thing to note here is that these properties and methods are added at the time of instantiation of the class rather than at the time of definition.

After the call statement is done executing, the loadedPercent property, which is exclusive to ProgressBarLoader, is added to the object.

Note: It is important to note that the call statement should be the first statement inside a subclass to avoid overriding of properties that exist in a subclass by the properties having the same name that exist in the parent class.

Invoking Parent Methods from Subclass

Consider an example where two classes that have a parent-child relationship have the same method name, but different implementations of the same method:

function Parent() {
}Parent.prototype.printMe = function() {
console.log('This is parent class!');
}function Child() {
Parent.call(this);
}Child.prototype.printMe = function() {
console.log('This is child class!');
}var child = new Child();
child.printMe(); // This is child class!

Child.prototype.printMe is called when printMe is invoked on a Childobject. What if we wanted to call Parent’s printMe on a Child object? We can achieve this by using call:

Parent.prototype.printMe.call(child);   // This is parent class!

We are directly invoking Parent.prototype.printMe. But it is invoked using call, which helps us in telling JavaScript that printMe has to be invoked in the context of the Child object.

This works but it is inconvenient to write Parent.prototype.printMe.callevery single time we want to invoke the parent class’ method. To fix this, let’s add a method to Child that provides a convenient API to call Parent’s printMe:

Child.prototype.printParentMe = function() {
Parent.prototype.printMe.call(this);
}var child = new Child();
child.printParentMe(); // This is parent class!

Object.create

Object.create creates a new object from an existing object with the new object having access to the properties of the existing object.

For example:

var loader = {
showLoader: function() {
console.log('loader is visible!');
},
hideLoader: function() {
console.log('loader is hidden!');
}
};var progressBarLoader = Object.create(Loader);progressBarLoader.showLoader(); // loader is visible!
progressBarLoader.hideLoader(); // loader is hidden!

So, what is Object.create doing under the hood? Is it copying everything from loader and adding it to progressBarLoader? No. It’s setting the __proto__ of progressBarLoader to loader!

When we invoke progressBarLoader.showLoader, JavaScript checks if showLoader exists on progressBarLoader. It doesn’t, so it goes up the prototype chain using progressBarLoader‘s __proto__ and finds the method there and invokes it.

In the subclass example, Object.create is used as follows:

ProgressBarLoader.prototype = Object.create(Loader.prototype);

Let’s see what would have happened if this statement weren’t there:

When progressBarLoader.showLoader is invoked, JavaScript first checks for the method on progressBarLoader. It doesn’t find it there and checks for it on the prototype of progressBarLoader. Now, what is the prototype of progressBarLoader? Its ProgressBarLoader and showLoader doesn’t exist on its prototype. Therefore, we use Object.create to add Loader.prototype to progressBarLoader‘s __proto__. Here’s how it looks:

Now, we can add stuff to ProgressBarLoader.prototype without affecting the Loader.prototype’s methods that were added using Object.create:

ProgressBarLoader.prototype.showLoadedPercent = function() {
console.log('progress %: ', this.loadedPercent);
}ProgressBarLoader.prototype.setPercent = function(percent) {
this.loadedPercent = percent;
}

Subclass.prototype.constructor

Let’s understand the significance of:

ProgressBarLoader.prototype.constructor = ProgressBarLoader;

Assume that this statement was not there in our example. What would ProgressBarLoader.prototype.constructor point to? Usually, when we create a function, say fn, then fn.prototype.constructor would point to fn itself. But ProgressBarLoader.prototype.constructor points to Loader because in the statement:

ProgressBarLoader.prototype = Object.create(Loader.prototype);

we set ProgressBarLoader.prototype to reference Loader.prototype and Loader.prototype.constructor is Loader! Why is this a problem? This Stack Overflow thread explains it perfectly. Therefore, we set ProgressBarLoader.prototype.constructor to ProgressBarLoader.

Note: Since ProgressBarLoader is considered a child class of Loader, one could assume ProgressBarLoader.constructor will be equal to Loader. However, that is not the case. It is still equal to Function just like Loader. This is because, as we learned, the constructor property points to the function constructor and the function constructor of ProgressBarLoader is still Function.

So, in JavaScript:

creating a subclass is just borrowing of methods and properties from a base class.

Only a prototypal link is created between the two classes and nothing else changes. This is unlike many object-oriented programming languages where there is a stricter sense of inheritance between a base class and a child class.

Usage of the Word ‘Prototype’

Whenever someone uses the word ‘prototype’ in a sentence, it could mean two things — the prototype keyword or __proto__. The way to figure out which of the two is being referred to is to understand the context in which it is being used. If we are talking about a function, the ‘prototype’ refers to the prototypekeyword. If the subject of discussion is an object, then ‘prototype’ refers to __proto__.

Wrapping It Up!

First of all, thanks for taking the time to read this!

The concepts we discussed today are complex and it usually takes time to grasp them, especially for people starting out with JavaScript. So if you still don’t feel 100% confident about them, don’t worry! It’s normal. It’s not your fault. It will likely take multiple reads from multiple sources — and a bit of actual experience of writing JavaScript code — to get close to 100.

If you have any questions or feedback, drop them in the comments! And if you feel the article helped you in any way, please show your appreciation by clapping for it! 😄