In modern JavaScript (ES6+), the extends and super keywords provide a clean, declarative syntax for implementing prototypal inheritance. While JavaScript remains a prototype-based language under the hood, this “syntactic sugar” allows developers to use familiar Class-Oriented patterns to create a hierarchy where a child class (subclass) inherits the properties and methods of a parent class (superclass).
1. The extends Keyword
The extends keyword is used in a class declaration or class expression to create a class as a child of another constructor.
When a class extends another, it automatically gains access to:
- Prototype Methods: All methods defined in the parent class.
- Static Methods: All static methods defined in the parent class.
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
// Dog "is-a" type of Animal
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const myDog = new Dog("Buddy");
myDog.eat(); // Inherited method: "Buddy is eating."
myDog.bark(); // Subclass method: "Woof! Woof!"
2. The super Keyword
The super keyword serves two distinct purposes depending on where it is called: as a function call in the constructor or as a reference to the parent object in methods.
A. super() in the Constructor
In a subclass, you must call super() before you can use this. Why? Because the parent class is responsible for initializing the base object. Calling super() executes the parent’s constructor and binds the resulting instance to this.
class Bird extends Animal {
constructor(name, wingSpan) {
super(name); // Calls Animal's constructor(name)
this.wingSpan = wingSpan; // Now 'this' is safe to use
}
}
[!IMPORTANT] If you define a constructor in a subclass, failing to call
super()will result in aReferenceErrorwhen you attempt to accessthis.
B. super.methodName() (Method Overriding)
Sometimes a subclass needs to modify a parent’s method rather than replacing it entirely. super allows you to call the original parent version of a method from within the subclass version.
class Cat extends Animal {
eat() {
super.eat(); // Run the original logic
console.log(`${this.name} cleans its paws after the meal.`);
}
}
3. Method Overriding and Shadowing
When a child class defines a method with the exact same name as a method in the parent class, it overrides the parent’s version. When you call that method on a child instance, the child’s version is executed. This is a core tenet of polymorphism.
class Shape {
draw() { console.log("Drawing a generic shape"); }
}
class Circle extends Shape {
draw() { console.log("Drawing a circle"); } // Overrides Shape.draw()
}
4. Inheriting Static Methods
Inheritance isn’t limited to instance methods. Static methods (methods called on the class itself, not an instance) are also inherited. If a parent class has a static utility, the child class can access it directly.
class Shape {
static createDefault() {
return new this("Generic Shape");
}
}
class Circle extends Shape {}
const myCircle = Circle.createDefault();
console.log(myCircle instanceof Circle); // true
5. Real-World Use Case: Custom UI Components
Inheritance is the backbone of component-based architectures. You might have a base Component class that handles rendering logic, and specific UI elements that extend it.
class UIComponent {
constructor(id) {
this.element = document.getElementById(id);
}
hide() {
this.element.style.display = 'none';
}
}
class Dropdown extends UIComponent {
toggle() {
const isHidden = this.element.style.display === 'none';
this.element.style.display = isHidden ? 'block' : 'none';
}
}
