Skip to Content
Course content

75: Class Inheritance with extends and super

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this specific error trip up almost every developer I've mentored when they first dive into class inheritance. You're trying to build something modular, you've got your base class set up, and suddenly JavaScript throws a ReferenceError that feels like a riddle. Let's look at a realistic scenario: you're building a simple RPG combat system.

class Character {
  constructor(name, health) {
    this.name = name;
    this.health = health;
  }

  takeDamage(amount) {
    this.health -= amount;
    console.log(`${this.name} took ${amount} damage!`);
  }
}

class Mage extends Character {
  constructor(name, health, mana) {
    this.mana = mana; // ❌ ReferenceError: Must call super constructor in derived class before accessing 'this'
  }
}

const gandalf = new Mage("Gandalf", 100, 50);

The "Must Call Super" Wall

If you try to run that code, it crashes immediately. You'll see a message telling you that you must call the super constructor before accessing this. It feels counterintuitive—you're in the Mage constructor, so why can't you just set this.mana right away?

Here is the deal: when a class extends another, the child class doesn't actually have its own this context until the parent class has had a chance to initialize it. Think of the Character class as the foundation of a house. You can't start painting the walls of the second floor (the Mage specific properties) until the first floor (the Character properties) has been built. JavaScript enforces this strictly to ensure that any logic in the parent constructor happens before the child starts messing with the object.

Wiring Up the Parent with super()

To fix this, we use the super keyword. Calling super() inside the constructor invokes the parent class's constructor. Not only does this satisfy the language requirement, but it also allows us to pass arguments up the chain so we don't have to manually re-assign name and health in every single subclass.

class Mage extends Character {
  constructor(name, health, mana) {
    super(name, health); // ✅ This calls the Character constructor
    this.mana = mana;    // Now 'this' is available!
  }
}

const gandalf = new Mage("Gandalf", 100, 50);
console.log(gandalf.name); // "Gandalf"
console.log(gandalf.mana); // 50

By calling super(name, health), we told JavaScript: "Go run the Character constructor using these two values, set up the name and health, and then come back here so I can finish setting up the mana."

Extending Methods Without Deleting Them

Inheritance isn't just about the constructor; it's about behavior. Sometimes you want a subclass to do everything the parent does, but with a little extra twist. You can use super inside regular methods too, not just the constructor.

Imagine our Mage has a magic shield that reduces damage. If we just rewrite takeDamage, we lose the logic of subtracting health and logging the message. Instead, we can call the parent's version of the method first:

class Mage extends Character {
  constructor(name, health, mana) {
    super(name, health);
    this.mana = mana;
  }

  takeDamage(amount) {
    const reducedAmount = amount - 5; // Magic shield reduces damage by 5
    super.takeDamage(reducedAmount); // Use the parent's logic to handle the actual subtraction
    console.log(`${this.name} used a mana shield!`);
  }
}

I prefer this approach because it follows the "DRY" (Don't Repeat Yourself) principle. If you ever decide to change how takeDamage works for all characters (maybe adding a "death" check), you only have to change it in the Character class, and the Mage will automatically inherit that update while keeping its unique shield logic.




📋 Practical Task

Building an Electric Vehicle Hierarchy

You are tasked with creating a vehicle management system. Your goal is to implement a base class and a specialized subclass using inheritance.

  • Create a base class called Vehicle. It should have a constructor that accepts make and model and assigns them to the instance. It should also have a method called startEngine that console logs: "The [make] [model] engine is starting..."
  • Create a subclass called ElectricCar that extends Vehicle.
  • The ElectricCar constructor should accept make, model, and batteryCapacity. It must correctly call the parent constructor using super.
  • Override the startEngine method in ElectricCar. It should first call the parent's startEngine method using super, and then log an additional message: "Silent power engaged! Battery at [batteryCapacity]%."
  • Instantiate an ElectricCar (e.g., "Tesla", "Model 3", 90) and call its startEngine method to verify both logs appear in the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.