Skip to content
wannacode

Inheritance

Problem

Two copies of the same code

The payroll program has an Employee class with a name, a salary and a method for a raise. Then you needed a manager too, who also gets a bonus. The fastest way was to copy Employee into a new class Manager and add the bonus.

A month later someone enters a raise of -10% instead of 10%. So you add a check to Employee. You forget it in Manager:

// in Employee
raise(percent) {
if (percent > 0) {
this.#salary += Math.trunc(this.#salary * percent / 100);
}
}
// in Manager: a copy from before the check
raise(percent) {
this.#salary += Math.trunc(this.#salary * percent / 100);
}
const ivan = new Employee("Ivan", 2000);
const maria = new Manager("Maria", 3000, 500);
ivan.raise(-10);
maria.raise(-10);
console.log(String(ivan));
console.log(String(maria));
Output
Ivan: €2000
Maria: €2700

Ivan's salary is protected, but Maria's dropped by €300. The two classes are 90% the same, yet every fix has to be made twice.

The idea

The shared part goes in the parent

A manager is an employee, just with something extra. Say it in code: class Manager extends Employee. In Manager you write only what is new, the bonus:

class Manager extends Employee {
#bonus;
constructor(name, salary, bonus) {
super(name, salary);
this.#bonus = bonus;
}
get bonus() {
return this.#bonus;
}
}
const maria = new Manager("Maria", 3000, 500);
maria.raise(-10);
maria.raise(10);
console.log(String(maria));
console.log(`Bonus: €${maria.bonus}`);
Output
Maria: €3300
Bonus: €500

Manager now has everything Employee has: a name, a salary, raise() with the check, toString(). That is why the -10% raise did not go through. The check is written once and works for both classes.

This is called inheritance. Employee is the parent (base class), and Manager is the child. The child takes everything from the parent and adds its own.

To check whether inheritance makes sense, use the sentence "X is a Y". "A manager is an employee" is true. "An invoice is an employee" is not, so the invoice does not inherit from Employee, even if it has an amount and a name.

Try it

What does the intern get?

Intern has nothing of its own: the body is empty, {}.

What will the program print?

class Intern extends Employee {}
const ana = new Intern("Ana", 1200);
ana.raise(5);
console.log(String(ana));
Answer

The idea

The parent comes first

#name and #salary are hidden in Employee. The child has them but cannot see them: to the child they are hidden, as they are to any other class. So how does Manager give them a value?

By calling the parent constructor:

constructor(name, salary, bonus) {
super(name, salary);
this.#bonus = bonus;
}

super(name, salary) calls the Employee constructor with these arguments. It sets up #name and #salary, and then Manager sets up #bonus. In JavaScript, super(...) has to be called before the constructor touches this. If you forget it, new Manager(...) stops with ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor.

There is one object. It holds both the parent's part and the child's part, and each part is set up by its own constructor.

constructor(name, salary, bonus) {
super(name, salary);
this.#bonus = bonus;
}
const maria = new Manager("Maria", 3000, 500);
console.log(`${maria}, bonus €${maria.bonus}`);
Step by step, in words
  1. new creates one object. It will hold the Employee part and the Manager part.
  2. The Manager constructor starts with the three arguments.
  3. The parent comes first: super(name, salary) calls the Employee constructor. Before it, you cannot touch this.
  4. The Employee constructor sets up its part: #name and #salary.
  5. Only then does the body of Manager set up the bonus.
  6. There is one object, and each part is set up by its own constructor.
More precisely, if you are curious
  • The constructor is inherited. That is why Intern, with no constructor of its own, takes the name and the salary of Employee: JavaScript calls the parent for you with the arguments you passed.
  • Once you write your own constructor, super(...) is your job. The child's fields (#bonus) are created only after super(...) is done.

Try it

Which constructor runs first?

Here the child does not call the parent explicitly:

What will the program print?

class Account {
constructor() {
console.log("Account opened");
}
}
class SavingsAccount extends Account {
constructor() {
console.log("Interest: 3%");
}
}
const savings = new SavingsAccount();
Answer

Try it

Find the bug

Maria should have a salary of €3000 and a bonus of €500. But the program prints:

Output
Maria: €500

Which line is the bug on?

Pick the line with the bug.

The idea

No protected: an underscore

At the end of the year, the manager's bonus is added to the salary. The method is in Manager, but the salary is #salary in Employee. The getter only reads it, and here you have to change it. And this.#salary is not allowed in Manager: the program does not even start, SyntaxError: Private field '#salary' must be declared in an enclosing class.

In Java and C# there is a third access level for this, protected: the class and its children can see it, and everyone else cannot. JavaScript does not have it. Instead, you write the field with an underscore, _salary: a convention for "internal, for the class and its children" that the language does not check:

class Employee {
#name;
constructor(name, salary) {
this.#name = name;
this._salary = salary;
}
// ... the rest is as before, with this._salary
}
class Manager extends Employee {
// ... #bonus and the constructor
mergeBonus() {
this._salary += this.#bonus;
this.#bonus = 0;
}
}
const maria = new Manager("Maria", 3000, 500);
maria.mergeBonus();
console.log(String(maria));
console.log(`Bonus: €${maria.bonus}`);
Output
Maria: €3500
Bonus: €0

The convention is not protection: maria._salary = 0 in the program also goes through, with no error. So everything the child should not touch stays in # (#name), and only the field it really has to change gets an underscore.

More precisely, if you are curious
  • # is real protection: the field cannot be reached from outside or from the children. That is also why the check happens before the program starts, not while it runs.

Try it

What is missing?

An intern is an employee. What goes in the blank?

class Intern  Employee {}

The idea

One parent and a common ancestor

A class has exactly one parent. It cannot inherit from both Employee and Student. That is why inheritance is single.

The chain can be long. Intern inherits from Employee. And what does Employee inherit from, since it has no extends? Every class without a parent inherits from Object. This is the common parent of all classes. That is why even an empty class has methods:

class Ticket {}
const ticket = new Ticket();
console.log(ticket.toString());
console.log(ticket);
Output
[object Object]
Ticket {}

toString() comes from Object and returns [object Object]. In the "Classes and objects" module you wrote your own toString(). Now you know that this replaces the version from Object. There is more on this in the next lesson.

console.log(ticket) does not call toString(). It shows the class and the fields: Ticket {}. That is why in this lesson we print String(maria): String() calls toString().

More precisely, if you are curious
  • In JavaScript there is no class that cannot be inherited from.
  • class WorkingStudent extends Employee, Student does not start: SyntaxError. Exactly one class goes after extends.

Exercise

Child classes

Solve the tasks in your editor. The parent classes are from the "Classes and objects" module. If you do not have them at hand, write them again: they are short.

1. Family car. SimpleCar has a speed and a method accelerate() that raises it by 10. Write FamilyCar, which inherits from SimpleCar, starts with 4 seats and has a method addSeat(). Create a family car, speed it up and add a seat:

Output
Speed: 10 km/h
Seats: 5

2. Student. Person has a name, an age and introduce(). Write Student, which inherits from Person and adds an average grade grade (a decimal number). Use the parent constructor.

Output
I am Elena, 19 years old.
Grade: 5.5

3. Breakdance. DanceMove has a name, a duration in seconds and perform(). Write BreakDanceMove with a complexity from 1 to 10 and a method showComplexity().

Output
Performing moonwalk for 5 seconds
Performing windmill for 12 seconds
windmill: complexity 8 out of 10

4. Electric car. Car has a brand, fuel in liters and the kilometers driven. refuel(liters) adds fuel, and drive(distance) uses 1 liter per 10 km and adds the kilometers (if there is not enough fuel, it says so and does not go). Write ElectricCar:

  • with no fuel, and with a battery that starts at 100%;
  • with an efficiency: how many km it drives on 1% of battery (5 for the "Tesla");
  • charge() charges the battery to 100%;
  • driveElectric(distance) uses battery instead of fuel and also adds the kilometers.
If the kilometers are #mileage in Car, the child cannot change them. Hint: the card on the underscore.
Output
Skoda: 150 km, fuel 35 L
Tesla: 200 km, battery 60%
Not enough battery for 400 km
Tesla: 600 km, battery 20%

More tasks

  • Worker. Worker inherits from Person and adds a job title and a method work():
Output
I am Peter, 34 years old.
My job: accountant
  • Intern with a mentor. Add a field with the mentor's name to Intern and pass it in the constructor, together with the name and the salary for the parent. Now Intern does need its own constructor.

Test

Check yourself

1. class Manager extends Employee. Which is true?
2. The field #name is in Employee. Can Manager write this.#name = "Maria" in one of its methods?
3. What does super(name, salary) do in the Manager constructor?
4. How many parents can a class have?
5. JavaScript has no protected. How do you write a field that the children use and everyone else should leave alone?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·