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 constructor
s 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!