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:
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 Charactermeans "a Wizard is a kind of Character."Wizardis the child class (or subclass), andCharacteris the parent class (or superclass). A Wizard automatically gets everything a Character has.- In the child's constructor, you must call
super(...)before usingthis. That runs the parent's constructor, which sets up the inherited properties (nameandhealth). Wizardadds a brand-new method,castSpell, that only wizards have.Wizardalso overridesattack: it defines its own version, which replaces the parent's version for wizards.Healerdoesn't have a constructor at all, so it automatically uses the parent's. It adds ahealmethod.Healeroverridesdescribe, but callssuper.describe()to reuse the parent's version, then adds something extra on the end.- A wizard is an
instanceofbothWizardandCharacter.
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.