Inheritance: extends and super

Sophomore Year, page 6 of 11. A two-minute read.

Let's say our game has different kinds of characters: warriors, wizards and healers. They all have a name and health and can attack (the things every Character has), but each kind also has its own special abilities.

Instead of rewriting everything for each kind, we can create classes that inherit from Character, using extends:

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

  attack() {
    return `${this.name} attacks!`;
  }

  describe() {
    return `${this.name} (${this.health} HP)`;
  }
}

class Wizard extends Character {
  constructor(name, health, mana) {
    super(name, health); // run the parent's constructor first
    this.mana = mana;
  }

  castSpell(spell) {
    this.mana -= 10;
    return `${this.name} casts ${spell}! 🔮 (mana left: ${this.mana})`;
  }

  // override the parent's method
  attack() {
    return `${this.name} zaps with a staff! ⚡`;
  }
}

class Healer extends Character {
  heal(target) {
    target.health += 25;
    return `${this.name} heals ${target.name}. 💚`;
  }

  describe() {
    return `${super.describe()} [healer]`;
  }
}

const merlin = new Wizard("Merlin", 70, 50);
const noah = new Healer("Noah", 90);

console.log(merlin.attack());
console.log(merlin.castSpell("Fireball"));
console.log(noah.attack());
console.log(noah.heal(merlin));
console.log(merlin.describe());
console.log(noah.describe());
console.log(merlin instanceof Wizard, merlin instanceof Character);

It prints

Merlin zaps with a staff! ⚡
Merlin casts Fireball! 🔮 (mana left: 40)
Noah attacks!
Noah heals Merlin. 💚
Merlin (95 HP)
Noah (90 HP) [healer]
true true

There's a lot going on, so let's unpack it:

  • class Wizard extends Character means "a Wizard is a kind of Character." Wizard is the child class (or subclass), and Character is the parent class (or superclass). A Wizard automatically gets everything a Character has.
  • In the child's constructor, you must call super(...) before using this. That runs the parent's constructor, which sets up the inherited properties (name and health).
  • Wizard adds a brand-new method, castSpell, that only wizards have.
  • Wizard also overrides attack: it defines its own version, which replaces the parent's version for wizards.
  • Healer doesn't have a constructor at all, so it automatically uses the parent's. It adds a heal method.
  • Healer overrides describe, but calls super.describe() to reuse the parent's version, then adds something extra on the end.
  • A wizard is an instanceof both Wizard and Character.
TipIs it really "a kind of"?

Use inheritance when the child truly is a kind of the parent: a Wizard is a Character, a Dog is an Animal, a SavingsAccount is a BankAccount. Don't use it just to share code between unrelated things. A Car is not a kind of Engine, even though a car has an engine. Also, keep inheritance chains short. One or two levels is usually plenty. Deep family trees of classes get confusing fast.

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning