const - JS | lectureJavaScript OOP

Inheritance Between "Classes" - ES6 Classes

JavaScript OOP

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.

index.js
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.

index.js
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.

index.js
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.

index.js
class Engineer extends Employee {
  constructor(name, dateOfBirth, department, projects, position) {
    Employee.call(...); // ❌ Don't do this  }
}

Instead, we call the super function.

index.js
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.

index.js
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:

index.js
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:

index.js
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:

index.js
class Engineer extends Employee {}

If we now try to create a new Engineer object, it will just work as expected.

index.js
const johnson = new Engineer('Shawn Johnson', 1990, 'Engineering');
console.log(johnson);

Inheritance between ES6 classes

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.

index.js
const little = new Engineer(
  'Rebecca Little',
  1990,
  'Engineering',
  ['Web Scraping', 'Data Cleaning'],
  'Senior Data Engineer'
);

console.log(little);

Inheritance between ES6 classes

Let's now also add the present method we had in the previous lecture.

index.js
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.

index.js
little.present();

Inheritance between ES6 classes

The same should also work for the calculateAge method.

index.js
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 😉.

index.js
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.

index.js
little.calculateAge();

Inheritance between ES6 classes

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.