Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Introduction to inheritance in JS

Get to know the fundamentals of inheritance in JavaScript that will make you rock the interviews.
27 11 2018

Due to the fact that inheritance is one of core concepts of Object Oriented Programming, it is also a common interview question. Thus, it is good to know the basic ideas behind it. Inheritance is the way of sharing properties between objects or classes. When a child object can access members (properties or methods) defined in a parent object, we can say that the child inherits from parent.

If you have experience with JavaScript, you’ve probably heard some of these statements:

  • “There are no classes in JS”
  • “Classes in JS are only syntactic sugar over prototypical inheritance”
  • “You should not use class in JS”


Why those ideas are widespread? What does it mean by there being “no classes in JS”?

Today I’ll give you an brief overview of inheritance in JS and try to answer those and other doubts.

“There are no classes in JS.”

Firstly, let’s face up to this sentence above, is it the truth? If you are familiar with React, the following code of stateful component will be well known to you:

class Home extends React.Component {...}


In modern JS, thanks to ES6 we are able to use the word  “class” to achieve something similar to classes known from another programming languages like Java or C#. Nevertheless, you have to be aware that this concept was added recently and it was not the default solution for inheritance in JS.

Let’s write the simplest code using class:

class Apple {
   constructor(color) {
       this.color = color;
   }
}


Next, we can visit the Babel web site to use a transpiler and see how the classes are interpreted by JS engines.

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Apple = function Apple(color) {
   _classCallCheck(this, Apple);

   this.color = color;
};


Don’t be scared by this function on top - it’s only a protection that will throw an error if the programmer did not use a new word before a class name while declaring a new object.

The protection prevents developers from extending global objects. The rest is a typical constructor function.

Next, we add a method to our Apple class. This is a simple method using one console.log statement to see how methods work in JS classes.

class Apple {
   constructor(color) {
       this.color = color;
   }

   grow() {
       console.log('I’m growing')
   }
}


After that, we define a new object:

const redDelicious = new Apple('red')


And print it in the console:

Looking at the data above, we can see that the properties inside the constructor become instance members of an object (stored in the object). The methods added outside the constructor can also be called prototype members (stored in the prototype). The most important conclusion from this part of the article is nonetheless represented by the 2nd sentence we quoted at the beginning:

Classes is JS are only syntactic sugar over prototypical inheritance.

Finally, once we know how classes work, we can then move on to how inheritance as a whole works in Java Stript.

Inheritance

If we wanted to write this code without classes it would be like this:

function Apple(color) {
   this.color = color
}

Apple.prototype.grow = function () {
   console.log('I’m growing')
}

const redDelicious = new Apple('red')


As a perceptive reader, you may notice that the first part looks exactly as our Babel output. A constructructor function is one of the ways of creating objects in JS. As for the second part, well there is a reason why we extend the prototype instead of including the method inside constructor. We extend the prototype of constructor function in order to optimize. If we had a thousand objects created by this constructor function, every object would store this method. Instead of that, we save the method “one level higher”. When we call this method, the JS engine looks for it in the object itself, followed by the constructor function’s prototype.

Look at the graph below - it is a basic example of the visualisation of inheritance.

When we declare the constructor function, Apple inherits some properties from one of the fundamental object in JS - Function.prototype. The Function does however inherit some properties from the fundamental Object.prototype.

When we declare redDelicious, the Apple.prototype is called to create [[Prototype]] in redDelicious.

You may ask yourself “what’s the difference between [[Prototype ]] and .prototype property?”

In JS we have a three kinds of properties:

  • Properties - properties of objects which are mapping string keys to values
  • Accessors - getters/setters whose invocation can looks like writing or reading properties
  • Internal properties - properties exist only in ECMAScript specification. You can’t access them directly from JS, but there might be a indirect way of accessing them. By definition in ES specification we write the keys of internal prop in brackets like [[ Prototype ]].
    A [[ Prototype ]] property is readable via Object.getPrototypeOf(). However, you can see the __proto__ prop in Chrome browser console.


So, a [[ Prototype ]] is an internal property which holds a prototype of the object used to create this new object (remember functions in JS are also an objects). So as was referred to earlier, every object has  a [[ Prototype ]] prop. On the other hand, a .prototype property is an object inherited from Function.prototype and it is a “prototype to install” when the new object is created.

Classical vs Prototypal inheritance

Classical inheritance is all about classes and ways of sharing methods and properties between other classes and objects. It will let us to reuse code in different parts of an app.  Bearing this in mind, let’s imagine that we have an Apple class with a grow() function. Then we would add a new class (e.g Pear) and we would use a grow() function in this class too. It is possible to just copy the method to Pear class. However, the main disadvantage of this solution is visible when we want to debug or change this function, as we have to make changes in multiple places. Would it not be better instead of copying the function, to create a parent class called e.g Fruit and add a grow() method to this class and allow the Apple and Pear classes to use this method? This kind of relationship we can call the IS-A relation. An Apple is a Fruit. The Fruit class can be labelled as a Super/Parent class and the Apple along with Pear is a Sub/Child class.

Nevertheless, before ES6 there were no classes in JS. So how we can manage inheritance when we only have objects? In the same way that we had classes, in prototype inheritance we define objects like apple and fruit and link them together to allowing  the use of common methods and properties. In that scenario we refer to a fruit object as prototype of apple. The main difference between classical and prototypal inheritance is as follows:
Class is a blueprint, it is a base from which to build objects, but you can’t use the class itself. On the other hand a Prototype is a pattern, a working object and it can be linked with other objects.

It’s worth mentioning that all objects in JS have a prototype, but sometimes it can be null. Let’s see some examples:

So go to the console and declare an empty object:

Next print this object on the console:

What we can see is a __proto__ property. It is a depreciated property and you, as a developer, should not use it in your code. Nevertheless, here I want to show you some basics of object relations - so it's enough for this purpose.

After doing that, we can see in __proto__ that the constructor of an object (the function used to create) is an Object - it is a root object for all objects in JS and as such its __proto__ or to be more precise [[Prototype]] - is null.

We can easily check that all objects inherit from the root Object. Instead of a depreciated __proto__ we use Object.getProtototypeOf()

Furthermore, we can see that from Object.prototype we get access to some methods like .toString. Therefore we can understand more about the concept from the first part of the article.

Let’s go back to the redDelicious object. How it is possible that we can use .toString even if we did not define this method in the object directly?

I’ll print on console the redDelicous object.

What can be read from console above? There is a redDelicious object which has a prototype - that's our constructor Apple function. Finally the constructor function has the well-known root Object as a prototype.

Going back to the .toString method, if a method or property is not found  in the object (as an instance member), the lookup is delegated to a prototype (as a prototypal member), which may have a link to its own prototype.  Therefore it continues up the chain until you arrive at Object.prototype. That's the inheritance in action.

Key things to remember:

Functions in JS are objects - the functions inherit from the Object.prototype.

If the property or method can’t be found in the object itself, the JS engine will look for it in the chain of prototypes.

Class is a blueprint and can’t be used directly. The Prototype is a valid object chainable with other objects.

Dear reader,  you have to be aware that in this article we have barely scratched the surface of inheritance in JS.

I’ve nonetheless tried to show a few basic rules and ideas. To do so, I had to omit a huge deal of information. For example, you were not able to read about polymorphism - an another key concept of object oriented programming that’s connected with method overwriting. There is also a lack of info on good practises and threats connected with classical and prototypal inheritance. Finally, we haven’t delved deeper into the classes in JS.

Inheritance is a complex concept and I recommend every JS developer explore this area. However, I hope that this brief overview was useful and will let you become a better web developer.

Referring back to to quoted sentences we began with, it’s not true that you should avoid classes in JS.

However it is good to know how the classes work and that there are some common problems connected with classical inheritance. Last but not least, it’s worth bearing in mind that, at least in particular cases, resigning from classes and replacing them by prototypical inheritance can possibly be a better solution.