Indermohan Singh JavaScript Developer interested in @angular @rxjs and Ionic framework.

Inheritance with JavaScript prototypes

6 min read 1897

TL;DR: In this post, we will look at prototypes and how to use them for inheritance in JavaScript. We will also see how the prototypical approach is different from class-based inheritance.

Inheritance

Inheritance, a prominent feature of a programming language, emerged with the introduction of object-oriented programming languages. Most of these languages were class-based languages. Here, class is like a plan or blueprint and objects are its manifestation. Meaning, in order to create an object, first we have to create a class. Then we can create any number of objects from one class.

Imagine, we have a class that represents a smartphone. This class has features like capturing images, GPS, etc, like any other smartphone. Here’s an example of how to create such a class and an object in C++ :
cpp class

We created a class named SmartPhone and it has a method named capturePictures, to capture images.

Let’s imagine, we need an iPhone class, that would capture images along with some special features like a face ID scan. Here are two possible solutions:

  1.  Rewrite the captureImages feature along with other common smartphone features, plus iPhone specific features into a new class. But this approach takes more time, effort and can introduce more bugs.
  2. Reuse features from the SmartPhone class. This is where inheritance comes into play. It’s a way to reuse features from other classes/objects.

Here is how we can inherit capturePictures method from the SmartPhone class, in our new Iphone class, in C++ :

inherit capturePictures method from SmartPhone class

Above is a trivial example of inheritance. However, it shows that inheritance allows us to reuse code in a way that the resulting program is less error-prone and takes less time to develop.

Here are some important things to know about classes :

  1. A class that inherits the feature is called as a child class
  2. A class from which features are inherited is called a parent class
  3. A class can inherit from multiple classes at once. For instance, class C inherits from class A and class B
  4. We can have multiple levels of Inheritance. For instance, class C inherits from class B and, class B inherits from class A

It’s worth to note that, class in itself is not doing anything. Until you create an object from a class, no work is actually done. We will see why it’s different from JavaScript.

What is a prototype?

In JavaScript, all objects have a special internal property which is basically a reference to another object. This reference depends upon how the object is created. In ECMAScript/JavaScript specification, it is denoted as [[Prototype]].

Since [[Prototype]] is linked to an object, that object has its own [[Prototype]] reference. This is how a chain is built (it’s known as the prototype chain).

This chain of [[Prototype]] is the building-block of inheritance in JavaScript.

__proto__ object

To access the object’s [[Prototype]], most of the browsers provide a __proto__ property.

This is how we can access it:

// obj is an actual object
obj.__proto__

It’s important to note that, this property is not a part of the ECMAScript standard. It is a de-facto implementation by the browsers.

Get and set prototype methods

Apart from the __proto__ property, there is a standard way to access the [[Prototype]].

Here is how we can access the [[Prototype]] of an object:

Object.getPrototypeOf(obj);

There is a similar method to set the [[Prototype]] of an object. This is how we do it:

Object.setPrototypeOf(obj, prototype);

[[Prototype]] and .prototype property

We have now discussed [[Prototype]]. It’s nothing but a standard notation to designate the prototype of an object. Many developers get it confused with .prototype property, which is an entirely different thing.

Let’s explore the .prototype property.

In JavaScript, there are many ways of creating an object. One way is using a constructor function, by calling it using the new keyword like this:

constructor

When you console.log the phone object, you will see an object with __proto__ property, like this:

phone log

Now, if we want to have some methods on the phone object, we can use .prototype property on the function, as follows:

const prototype

When we create the phone object again, we would see the following in the console.log:

phone log

We can see the isAndroid() method in the object’s [[Prototype]].

In short, the .prototype property is basically like a blueprint for the [[Prototype]] object created by the given constructor function. Anything that you declare in the .prototype property/object will pop up in object’s [[Prototype]].

As a matter of fact, if you compare the SmartPhone.prototype to the phone’s [[Prototype]], you will see that they are the same:

console.log(Object.getPrototypeOf(phone) === SmartPhone.prototype);
// true

It’s worth noting that, we can also create methods inside the constructor function. Instead, we did it using the function’s prototype. There’s a good reason to do so.

Let’s take a look at the following example:

prototype log

The problem with this approach is when we initiate a new object. All the instances get their own copy of methodA. On the contrary, when we create it on function’s prototype, all instances of the object share just one copy of the method. Which is more efficient.

What happens when we access a property?

When we access a property either to get it, the following happens:

  1. The JavaScript engine looks for the property on the object
    1. If it finds the property, then it returns it
  2. Otherwise, the JavaScript engine checks the inherited property of an object by looking at [[Prototype]]
    1. If the property is found, then it returns it
    2. Otherwise, it looks into [[Prototype]] of [[Prototype]]. This chain ends when either the property is found or there is no [[Prototype]] left, which means that we have reached the end of the prototype chain

When we set/create a property, JavaScript always set it on the object itself. Even if the same property exists on the [[Prototype]] chain. Here is an example:

function MyObject() {}
MyObject.prototype.propA = 10; // creating a property on the prototype

let myObject = new MyObject();
console.log(myObject.propA); // property on the [[Prototype]]
// 10

myObject.propA = 20; // property on the object
console.log(myObject.propA);
// 20

In the above example, we created a constructor function, which has a property propA on it’s [[Prototype]]. When we try to access it for the read operation, we see the value in the console. But when we try to set the same property on the object itself; JavaScript creates a new property on the object with the given value. Now if we want to access the property on the [[Prototype]] directly, we can’t. It’s called the shadowing of property.

It’s also worth noting that the end of a normal object’s [[Prototype]] chain is built-in Object.prototype. That’s the reason why most of the object shares many methods like toString(). Because they are actually defined on Object.prototype.

Various ways of using prototypical inheritance

In JavaScript, there is just prototypical inheritance. No matter how we create an object. But still, there are subtle differences, that we should take a look at.

Object literal

The easiest way to create an object in JavaScript is by using an object literal. This is how we do it:

let obj = {};

If we log the obj in the browser’s console, we will see the following:

object prototype

So basically, all the objects created with literal notation inherit properties from Object.prototype.

It’s also worth noting that __proto__ object has reference to the constructor function, from which it’s created. In this case, the constructor property points to Object constructor.

Using the Object constructor

Another, not-so-common way of creating an object is using Object constructor. JavaScript provides a built-in constructor method named Object to create Objects.

Here’s how we use it:

let obj = new Object();

This approach results in the same object as object literal notation. It inherits properties from Object.prototype. Since we use Object as a constructor function.

Object.create method

With this helper method, we can create an object with another object as it’s [[Prototype]] like this:

object create

This is one of the simplest ways to use inheritance in JavaScript.

Any guess how we can make an object without any [[Prototype]] reference?

Constructor method

Similar to how we have the object constructor function provided by JavaScript runtime. We can also create our own constructor, to create an object which suits our needs as we can see here:

function SmartPhone(os) {
  this.os = os;
}

SmartPhone.prototype.isAndroid = function() {
  return this.os === 'Android';
};

SmartPhone.prototype.isIOS = function() {
  return this.os === 'iOS';
};

Now, we want to create an iPhone class, which should have 'iOS' as it’s OS. It should also have the faceIDScan method.

First, we have to create an Iphone constructor function and inside it, we should call the SmartPhone constructor, like this:

function Iphone() {
   SmartPhone.call(this, 'iOS');
}

This will set the this.os property to 'iOS' in the Iphone constructor function.

The reason why we called SmartPhone.call method is because we need to change the value of this to refer to Iphone. It would be similar to calling the parent’s constructor in an object-oriented world.

The next thing is, we have to inherit methods from SmartPhone constructor. We can use our Object.create friend here, as follows:

Iphone.prototype = Object.create(SmartPhone.prototype);

Now we can add methods for Iphone, using .prototype as follows:

Iphone.prototype.faceIDScan = function() {};

Finally, we can create an object using Iphone as follows:

let x = new Iphone();

// calling inherited method
console.log(x.isIOS()):
// true

ES6 class

With the ES6, this whole ordeal is very simple. We can create classes (they are not the same as classes in C++ or other any class-based language, just a syntactical sugar on top of prototypical inheritance) and derive new classes from other classes.

Here is how we create a class in ES6:

class SmartPhone {
  constructor(os) {
    this.os = os;
  }
  isAndroid() {
    return this.os === 'Android';
  }
  isIos() {
    return this.os === 'iOS';
  }
};

Now we can create a new class which is derived from SmartPhone, like this :

class Iphone extends SmartPhone {
   constructor() {
     super.call('iOS');
   }
   faceIDScan() {}
}

Instead of calling SmartPhone.call, we are calling super.call. But internally, the JavaScript engine is doing this for us automatically.

Finally, we can create an object using Iphone as follows:

let x = new Iphone();

x.faceIDScan();

// calling inherited method
console.log(x.isIos()):
// true

This ES6 example is the same as the previous constructor method example. But it’s much cleaner to read and understand.

Conclusion

Let’s summarize what we have learned so far:

  • In class-based languages, we can’t run the classes. We have to create objects from them in order to get anything done
  • Inheritance in JavaScript isn’t the same as in class-based languages. Because there is no real concept of class. Objects inherit via a reference called as prototype
  • [[Prototype]] is just a fancy way of referring to an object’s prototype. They’re both the same thing
  • We can access an object’s prototype using either __proto__ property or Object.getPrototypeOf method
  • We found out that the function’s prototype property acts as a blueprint for the object’s [[Prototype]] which is created using the new keyword
  • We learned what happens when we access a property on an object and what role the prototype chain plays there
  • Finally, we also learned about multiple ways of creating an object in JavaScript

I hope this blog post was useful. To learn more about inheritance in JavaScript take a look at the article on MDN.

Plug: , a DVR for web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Indermohan Singh JavaScript Developer interested in @angular @rxjs and Ionic framework.

Leave a Reply