-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
75: Class Inheritance with extends and super
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 aconstructorthat acceptsmakeandmodeland assigns them to the instance. It should also have a method calledstartEnginethat console logs:"The [make] [model] engine is starting..." - Create a subclass called
ElectricCarthatextends Vehicle. - The
ElectricCarconstructor should acceptmake,model, andbatteryCapacity. It must correctly call the parent constructor usingsuper. - Override the
startEnginemethod inElectricCar. It should first call the parent'sstartEnginemethod usingsuper, and then log an additional message:"Silent power engaged! Battery at [batteryCapacity]%." - Instantiate an
ElectricCar(e.g., "Tesla", "Model 3", 90) and call itsstartEnginemethod to verify both logs appear in the console.
There are no comments for now.