JavaScript Inheritance & extends
Create specialized classes from parent classes using extends.
Basic Inheritance
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
let dog = new Dog("Rex");
console.log(dog.speak()); // "Rex barks"
Dog inherits name from Animal and overrides speak().
The super Keyword
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
describe() {
return `${super.describe()} (${this.breed})`;
}
}
Remember: Call super() before using this in a child constructor.
Method Overriding & Polymorphism
class Shape {
constructor(name) { this.name = name; }
getArea() { throw new Error("getArea() must be implemented"); }
toString() { return `${this.name} with area ${this.getArea()}`; }
}
class Circle extends Shape {
constructor(r) { super("Circle"); this.radius = r; }
getArea() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(w, h) { super("Rectangle"); this.w = w; this.h = h; }
getArea() { return this.w * this.h; }
}
[new Circle(5), new Rectangle(4, 6)].forEach(s => console.log(s.toString()));
// "Circle with area 78.539..."
// "Rectangle with area 24"
Same toString(), different results — this is polymorphism.
Complete Example: Employee System
class Employee {
constructor(name, salary) { this.name = name; this.salary = salary; }
getBonus() { return this.salary * 0.1; }
describe() { return `${this.name}: $${this.salary} + $${this.getBonus()} bonus`; }
}
class Manager extends Employee {
constructor(name, salary, dept) {
super(name, salary);
this.department = dept;
this.reports = [];
}
getBonus() { return this.salary * 0.2; }
addReport(emp) { this.reports.push(emp); return this; }
describe() { return `${super.describe()} (Manager of ${this.department})`; }
}
class Developer extends Employee {
constructor(name, salary, langs) {
super(name, salary);
this.languages = langs;
}
getBonus() { return this.salary * 0.15; }
describe() { return `${super.describe()} (Dev: ${this.languages.join(", ")})`; }
}
let dev = new Developer("Alice", 80000, ["JS", "Python"]);
let mgr = new Manager("John", 120000, "Engineering");
console.log(dev.describe()); // "Alice: $80000 + $12000 bonus (Dev: JS, Python)"
console.log(mgr.describe()); // "John: $120000 + $24000 bonus (Manager of Engineering)"
Each subclass overrides getBonus() with a different rate — same interface, different behavior.
Best Practices
- Use
extendsfor “is-a” relationships — Dog is an Animal - Call
super()first — Before usingthisin child constructor - Override methods carefully — Maintain the parent’s interface
- Keep hierarchies shallow — Deep inheritance is hard to maintain
- Prefer composition — “Has-a” often beats “is-a”
Common Mistakes
- Forgetting
super()— Required in child constructor - Overriding with different parameters — Breaks the interface contract
- Deep inheritance chains — Hard to maintain and debug
- Not returning
this— Breaks method chaining