Inheritance Between "Classes" - ES6 Classes
In this lecture, we will do exactly what we did in the previous lecture, but this time using ES6 classes. For that, let's first convert the Employee
constructor function to an ES6 class.
class Employee {
constructor(name, dateOfBirth, department) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.department = department;
}
calculateAge() {
console.log(2022 - this.dateOfBirth);
}
}
Before we convert our Engineer
constructor function to an ES6 class, we already know that the class syntax hides a lot of the details that are actually happening behind the scenes. Because classes are really just a layer of abstraction over constructor functions. But that's not a problem because we've already learned how inheritance between classes actually works behind the scenes.
To implement inheritance between ES6 classes, we only need two ingredients: the extends
keyword and the super
function. So to make the Engineer
class inherit from the Employee
class, we simply need to use the extends
keyword.
class Engineer extends Employee {}
With this, the extends
keyword will then link the prototypes behind the scenes without us having to think about it. Next, just like before, we need to define our constructor
function, which will receive the same arguments as the parent class but with some additional arguments.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) { // do something
}
}
Now, in the constructor function, to call the parent constructor function, we don't need to use the call
method as we did before.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
Employee.call(...); // ❌ Don't do this }
}
Instead, we call the super
function.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
super(...); // ✅ Do this }
}
The super
function is basically the constructor function of the parent class. The idea is still similar to what we did in the constructor function, but here it all happens automatically. We don't need to specify the name of the parent class again.
All we now need to do is to pass in the arguments of the parent constructor function.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
super(name, dateOfBirth, department); }
}
Note that the super
function call in the constructor function always needs to happen first! That's because the super
function call is responsible for creating the this
keyword in the subclass. Hence, if it doesn't happen first, we won't be able to access the this
keyword to do this:
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
super(name, dateOfBirth, department);
this.projects = projects; this.position = position; }
}
Also, in the child class, we could have decided that we didn't want to add any additional properties. So we could have something like this:
class Engineer extends Employee {
constructor(name, dateOfBirth, department) {
super(name, dateOfBirth, department);
// having additional properties is not mandatory
}
}
In this case, this class will simply have new methods and share all the properties with the parent class, and also, we won't need any constructor function at all. So we could have something like this:
class Engineer extends Employee {}
If we now try to create a new Engineer
object, it will just work as expected.
const johnson = new Engineer('Shawn Johnson', 1990, 'Engineering');
console.log(johnson);
So this was just an example to demonstrate that if you don't have any additional properties in the child class, you don't need to define a constructor function at all. Let's now go back to what we had before and create a new Engineer
object.
const little = new Engineer(
'Rebecca Little',
1990,
'Engineering',
['Web Scraping', 'Data Cleaning'],
'Senior Data Engineer'
);
console.log(little);
Let's now also add the present
method we had in the previous lecture.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
super(...);
//...
}
present() { console.log( `Hi, my name is ${this.name} and I'm a ${this.position} in the ${this.department} department.` ); }}
And now we can call this method on the Engineer
object.
little.present();
The same should also work for the calculateAge
method.
little.calculateAge(); // 32
This clearly proves that the prototype chain was set up automatically by the extends
keyword. Finally, let's override the calculateAge
method in the Engineer
class. Just so we can see polymorphism in action 😉.
class Engineer extends Employee {
constructor(name, dateOfBirth, department, projects, position) {
super(...);
//...
}
present() {
//...
}
calculateAge() { console.log(`I'm ${2022 - this.dateOfBirth} years old, but I look younger.`); }}
Let's call the calculateAge
method again on the Engineer
object.
little.calculateAge();
And now we can see that this new method overwrote the one that was already there in the prototype chain. And that's because this new calculateAge
method appears first in the prototype chain. Hence overriding the one coming from the parent class. We can also say that this new calculateAge
method is shadowing the one coming from the parent class.