Back

Inheritance in JavaScript object-oriented programming

Inheritance in JavaScript object-oriented programming

Inheritance in JavaScript Object-Oriented Programming (OOP) is a fundamental concept that has evolved over time, and in this article we’ll show how it works and how to use it.

We’re looking at true class inheritance, not simply prototypal inheritance between instances. The class terminology is used here only to make it simpler to grasp what we’re going to do. Here’s what we’re going to do, we’ll construct a new student class that inherits from the person class. The parent class will be a person, and the child class will be a student. This is because a student is essentially a subtype of a person.

A student is a person, a more specific individual, and this relationship is essentially the inheritance concept that we’ll explore in this article. To begin, we will use constructor functions. We will inherit across classes using constructor functions in the first phase, which will need some work but will help you understand exactly how the prototype chain is built up to allow inheritance between the prototype properties of two separate constructor functions. Then, in the following step, we’ll perform the same using ES6 classes, which will be much easier.

Finally, we shall make use of object.create. Let’s put this into action right now.

Inheritance between “Classes”: Constructor functions

A constructor function is being built for the student. We want the child class to have the same functionality as the parent class but with some additional functionality. We pass the same argument into the child class but then some additional ones.

const person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

person.prototype.calcAge = function () {
  console.log(2022 - this.birthYear);
};

const student = function (firstName, birthYear, course) {
  this.firstName = firstName;
  this.birthYear = birthYear;
  this.course = course;
};

The above shows that the student class has almost the same data as the Person.

We can then create a new student.

const paul = new student("Paul", 2010, "Computer Science");
console.log(paul);

We can also add a method by modifying the prototype property.

//Add in a method
student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

paul.introduce();

So far, this is what we’ve created. It is nothing more than the student constructor function and its prototype attribute. In addition, the paul object is connected to its prototype, and, of course, the prototype is the constructor function’s prototype attribute. By constructing the paul object using the new operator, this relationship between instance and prototype is now created automatically.

We are going to have to create this connection manually. To link these two prototype objects, we will use object.create because defining prototypes manually is precisely what object.create does. A student is a person; as a result, we want student and person to be linked. We are interested in the student class being a child class descended from the person class, the parent class. As a result, all occurrences of students will get access to everything in the prototype property, such as the calcAge function. That is the whole point of inheritance. The parent classes can share their conduct with their child classes.

We want to make person.prototype the prototype of student.prototype or, in other words, we want to set the proto property of student.prototype to person.prototype. We are going to have to create this connection manually. We will use object.create to link these two prototype objects because defining prototypes is exactly what object.create does. With this, the student.prototype object is now inheriting from the person.prototype. Now we have to create this connection before adding more methods to the prototype object of a student. That’s because object.create will return an empty object. At this point, student.prototype is empty. Then onto that empty object, we can add methods.

Now, with all this in place, we can call the calcAge method even in a child class.

const person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

person.prototype.calcAge = function () {
  console.log(2022 - this.birthYear);
};

const student = function (firstName, birthYear, course) {
  person.call(this, firstName, birthYear);
  this.course = course;
};

//Linking Prototypes
student.prototype = Object.create(person.prototype);
//Add in a method
student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and i study ${this.course}`);
};

const paul = new student("Paul", 2010, "Computer Science");
paul.introduce();
paul.calcAge();

We already know this worked because of the prototype chain but let’s see precisely how. When we do paul.calcAge(), we effectively do a property or a method lookup, JavaScript trying to find the requested property or method. The calcAge method is not directly on the paul object and is also not in paul’s prototype. Whenever we try to access a method not on the object or its prototype, JavaScript will look up even further in the prototype chain. JavaScript will finally find calcAge in person.prototype. That’s the whole reason why we set up the prototype chain like this so that the paul object can inherit whatever methods are in its parent class. In summary, we can now call a method on a person’s prototype property, on a student object, and it still works. That’s the power of inheritance.

So just like before, object.prototype will sit on top of the prototype chain. It doesn’t matter how far away in the prototype chain a method is; with this, we now have the full picture of how inheritance between classes works with function constructors.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

Inheritance between “Classes”: ES6 Classes

It works the same internally with ES6 classes; all that changes is the syntax.

Let’s copy the initial person class that we built earlier. So, we can inherit from the class.

const steven = Object.create(personProto);
const studentProto = Object.create(personProto);
class personCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2022 - birthYear);
  }
}

The class syntax hides a lot of the details happening behind the scenes because classes are just a layer of obstruction over constructor functions. To implement inheritance between ES6 classes, we only need two ingredients, which are the extends keywords and the super function.

The extends keyword alone will link to prototypes behind the scenes without even thinking about that. Then, of course, we still need a constructor. This will be like before, receive the same arguments as the parent class, plus some additional ones like birthYear and course. We don’t need to manually call personCl.call as we did before in the constructor function. What we do instead is to call the super function. The super function is the constructor function of the parent class. The idea is still similar to what we did in the constructor function, but it all happens automatically here. We don’t need to specify the name of the parent class again because that has already happened. All we do is pass in the arguments for the parent class constructor.

The call to the super function always needs to happen first because the call to the super function is responsible for creating the this keyword in the subclass. Still, the additional parameter and argument are not mandatory.

class studentCl extends personCl {
  constructor(fullName, birthYear, course) {
    //Always needs to happen first
    super(fullName, birthYear);
    this.course = course;
  }

  introduce() {
    console.log(`My name is ${this.fullName} and i study ${this.course}`);
  }
}

Now let’s create a new student,

const martha = new studentCl ('Martha', 2012, 'computer science')

Now, we can call the calcAge method and the introduce method.

martha.introduce();
martha.calcAge();

If we look at Martha’s prototype, we can see that the introduce and calcAge methods are there. This proves that the prototype chain was set up automatically by basically this extends keyword here.

Inheritance between “Classes”: Object.create

Now, let’s create an object that will serve as a prototype to create a new person object using object.create. This will be our parent class.

Object.create is easier to use and understand, and it won’t be hard to implement a similar hierarchy between a person and a student.

const personProto = {
  calcAge() {
    console.log(2022 - this.birthYear);
  },

  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

The personProto is the prototype of all the new person objects, but now we want to add another prototype in the middle of the chain. That’s between personProto and the object. We are going to make students inherit directly from person, and we will create an object that will be the prototype for students.

const studentProto = Object.create(personProto);
//we can use this student proto to create new students
const ray = Object.create(studentProto);

The studentProto object that we just created earlier is now the prototype of the ray object. Again, the studentProto object is now the prototype of ray, and the personProto object is, in turn, the prototype of studentProto. Therefore, personProto is a parent prototype of ray, which means it’s in its prototype chain.

It all starts with the PersonProto object, which used to be the prototype of all person objects but now using Object.create, we make it so that personProto will become the prototype of studentProto and what this does is that basically, student inherits from person. We already established the parent-child relationship that we were looking for. To finish, all we need to do is use Object.create again, but this time to create a new actual student object. ray inherits from studentProto, which is now ray’s prototype; therefore, the ray object will be able to use all the methods in studentProto and personProto.

With the scope chain correctly established, let’s also add an init method to studentProto, which will receive the firstName, birthYear, and course just like we did here with personProto so that we don’t have to manually specify the properties on any new student object.

studentProto.init = function (firstName, birthYear, course) {
  personProto.init.call(this, firstName, birthYear);
  this.course = course;
};

studentProto.introduce = function () {
  console.log(`My name is ${this.firstName} and i study ${this.course}`);
};

const ray = Object.create(studentProto);
ray.init("Ray", 2010, "computer science");
ray.introduce();
ray.calcAge();

If we look at the prototype of ray, we can see that the init and introduce methods are there. Then inside the inner prototype, the calcAge method can be found. In this Object.create we don’t worry about the constructor function or prototype properties, and it’s just objects linked to other objects. This is simple and beautiful if you ask me. In fact, some people think that this pattern is a lot better than basically trying to fake classes in JavaScript because faking classes in the way that they exist in other languages like Java or C++ is precisely what we do by using constructor functions and even ES6 classes. But here, in this technique that I just showed you with Object.create, We are, in fact, not faking classes.

We are simply linking objects together, where some objects then serve as the prototype of other objects. I wouldn’t mind if this were the only way of doing OOP in JavaScript, but ES6 classes and constructors are way more used in the real world. However, it’s still super important and valuable that you learn these three techniques now because you will see them all. This also allows you to think about this and choose the style you like the best.

Conclusion

Inheritance lets you design a class that inherits all of the functionality of a parent class and adds new capabilities. A class can inherit all of the methods and attributes of another class via class inheritance. In JavaScript, inheritance is supported by using prototype objects, and inheritance is a helpful feature that enables code reuse.

I hope you found this article on the inheritance Concept in JavaScript useful!

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay