Introduction
Inheritance lets classes build upon other classes—sharing code while allowing specialization. JavaScript's extends keyword makes inheritance straightforward, while super provides access to parent functionality.
Key Concepts
extends: Keyword to create a subclass that inherits from a parent.
super: Reference to the parent class (call constructor or methods).
Method Overriding: Redefining inherited methods in a subclass.
Real World Context
Component hierarchies, error types, specialized versions of base classes—inheritance models "is-a" relationships. TypeError extends Error, AdminUser extends User, Button extends Component.
Deep Dive
Basic Inheritance
javascriptclass Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // MUST call super() first! this.breed = breed; } // Override parent method speak() { console.log(`${this.name} barks!`); } // New method fetch() { console.log(`${this.name} fetches the ball.`); } } const dog = new Dog('Rex', 'German Shepherd'); dog.speak(); // 'Rex barks!' dog.fetch(); // 'Rex fetches the ball.' dog instanceof Dog; // true dog instanceof Animal; // true
Calling Parent Methods with super
javascriptclass Cat extends Animal { speak() { super.speak(); // Call parent's speak() console.log(`${this.name} also meows.`); } } const cat = new Cat('Whiskers'); cat.speak(); // 'Whiskers makes a sound.' // 'Whiskers also meows.'
Static Inheritance
javascriptclass Parent { static greet() { return 'Hello from Parent'; } } class Child extends Parent { static greet() { return `${super.greet()} and Child`; } } Child.greet(); // 'Hello from Parent and Child'
Extending Built-ins
javascriptclass ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } class TypedArray extends Array { constructor(type, ...items) { super(...items); this.type = type; } push(...items) { items.forEach(item => { if (typeof item !== this.type) { throw new TypeError(`Expected ${this.type}`); } }); return super.push(...items); } } const numbers = new TypedArray('number', 1, 2, 3); numbers.push(4); // OK // numbers.push('5'); // TypeError
Mixin Pattern (Composition over Inheritance)
javascript// Mixins - share behavior without inheritance const Serializable = (Base) => class extends Base { toJSON() { return JSON.stringify(this); } static fromJSON(json) { return Object.assign(new this(), JSON.parse(json)); } }; const Timestamped = (Base) => class extends Base { constructor(...args) { super(...args); this.createdAt = new Date(); } }; // Apply mixins class User extends Serializable(Timestamped(Object)) { constructor(name) { super(); this.name = name; } } const user = new User('Alice'); user.toJSON(); // Works! user.createdAt; // Works!
Common Pitfalls
- Forgetting
super()in constructor: Required before usingthis. - Deep inheritance hierarchies: Hard to understand and maintain.
- Overusing inheritance: Prefer composition for flexibility.
Best Practices
- Call
super()first in constructors: Before anythisusage. - Prefer shallow hierarchies: 2-3 levels max.
- Use composition over inheritance: When possible.
- Follow Liskov Substitution: Subclasses should be usable as parent class.
Summary
extends creates a subclass. super() must be called first in derived constructors. Use super.method() to call parent methods. Static methods are also inherited. Consider mixins for sharing behavior across unrelated classes. Keep hierarchies shallow.
Code Examples
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // MUST call super() first!
this.breed = breed;
}
// Override parent method
speak() {class Cat extends Animal {
speak() {
super.speak(); // Call parent's speak()
console.log(`${this.name} also meows.`);
}
}
const cat = new Cat('Whiskers');
cat.speak();
// 'Whiskers makes a sound.'
// 'Whiskers also meows.'