Docs
11.2-Class-Inheritance
8.2 Class Inheritance
Overview
Class inheritance allows you to create new classes based on existing ones. The child class inherits properties and methods from the parent class and can add or override functionality. ES6 provides clean syntax for inheritance using extends and super.
Table of Contents
- β’The extends Keyword
- β’The super Keyword
- β’Method Overriding
- β’Constructor Inheritance
- β’Prototype Chain with Classes
- β’Extending Built-in Classes
- β’The instanceof Operator
- β’Abstract Base Classes
The extends Keyword
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Class Inheritance β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β class Parent { β
β parentMethod() { } β
β } β
β β² β
β β extends β
β β β
β class Child extends Parent { β
β childMethod() { } β
β } β
β β
β Child instance has access to: β
β β’ childMethod() (own) β
β β’ parentMethod() (inherited) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Basic Inheritance
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
move(distance) {
console.log(`${this.name} moved ${distance} meters`);
}
}
class Dog extends Animal {
bark() {
console.log(`${this.name} barks: Woof!`);
}
}
const dog = new Dog('Rex');
dog.speak(); // "Rex makes a sound" (inherited)
dog.bark(); // "Rex barks: Woof!" (own method)
dog.move(10); // "Rex moved 10 meters" (inherited)
What Gets Inherited
| Inherited | Not Inherited |
|---|---|
| Instance properties | - |
| Prototype methods | - |
| Getters/Setters | - |
| Static methods* | Static properties are copied |
The super Keyword
super is used to call the parent class's constructor and methods.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β super Keyword Usage β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β In Constructor: β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β constructor(args) { β β
β β super(parentArgs); // Call parent ctor β β
β β this.childProp = value; β β
β β } β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β In Methods: β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β method() { β β
β β super.parentMethod(); // Call parent methodβ β
β β // Additional child logic β β
β β } β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
super in Constructor
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
// MUST call super() before using 'this'
super(name);
this.breed = breed;
}
}
const dog = new Dog('Rex', 'German Shepherd');
console.log(dog.name); // "Rex"
console.log(dog.breed); // "German Shepherd"
super in Methods
class Animal {
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
// Call parent method
const parentSpeech = super.speak();
return `${parentSpeech} and barks: Woof!`;
}
}
const dog = new Dog('Rex');
console.log(dog.speak());
// "Rex makes a sound and barks: Woof!"
Rules for super
| Rule | Description |
|---|---|
| Must call in constructor | If you define a constructor in child class |
Before this | super() must be called before accessing this |
| Only in derived class | Can't use super in non-extending class |
| Can access parent methods | super.method() calls parent version |
Method Overriding
Child classes can override parent methods with their own implementation.
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `A ${this.color} shape`;
}
area() {
throw new Error("Method 'area()' must be implemented");
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
// Override describe
describe() {
return `A ${this.color} circle with radius ${this.radius}`;
}
// Implement area
area() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
describe() {
return `A ${this.color} rectangle (${this.width}x${this.height})`;
}
area() {
return this.width * this.height;
}
}
const circle = new Circle('red', 5);
const rect = new Rectangle('blue', 4, 6);
console.log(circle.describe()); // "A red circle with radius 5"
console.log(circle.area()); // ~78.54
console.log(rect.describe()); // "A blue rectangle (4x6)"
console.log(rect.area()); // 24
Overriding with Extension
class Logger {
log(message) {
console.log(`[LOG]: ${message}`);
}
}
class TimestampLogger extends Logger {
log(message) {
const timestamp = new Date().toISOString();
// Extend parent behavior
super.log(`[${timestamp}] ${message}`);
}
}
const logger = new TimestampLogger();
logger.log('Application started');
// [LOG]: [2024-01-15T10:30:00.000Z] Application started
Constructor Inheritance
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Constructor Inheritance Rules β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Parent has constructor β Child should call super() β
β β
β Child with NO constructor: β
β β’ Automatically gets: constructor(...args) { β
β super(...args); β
β } β
β β
β Child with constructor: β
β β’ MUST call super() before using 'this' β
β β’ MUST call super() before returning β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Implicit Constructor
class Animal {
constructor(name) {
this.name = name;
}
}
// No constructor defined - uses default
class Dog extends Animal {
bark() {
console.log(`${this.name} barks!`);
}
}
// Equivalent to:
class Dog extends Animal {
constructor(...args) {
super(...args); // Automatically generated
}
bark() {
console.log(`${this.name} barks!`);
}
}
Constructor with Different Signature
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
class Car extends Vehicle {
constructor(make, model, doors = 4) {
super(make, model); // Call parent
this.doors = doors; // Add own property
}
}
class Motorcycle extends Vehicle {
constructor(make, model) {
super(make, model);
this.wheels = 2; // Fixed for motorcycles
}
}
Prototype Chain with Classes
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Class Inheritance Prototype Chain β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β dog instance β
β β β
β β [[Prototype]] β
β βΌ β
β Dog.prototype βββββββ { bark() } β
β β β
β β [[Prototype]] β
β βΌ β
β Animal.prototype ββββ { speak(), move() } β
β β β
β β [[Prototype]] β
β βΌ β
β Object.prototype ββββ { toString(), hasOwnProperty() } β
β β β
β β [[Prototype]] β
β βΌ β
β null β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Verifying the Chain
class Animal {
speak() {}
}
class Dog extends Animal {
bark() {}
}
const dog = new Dog();
// Instance checks
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// Prototype chain
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
Extending Built-in Classes
You can extend JavaScript's built-in classes.
Extending Array
class Stack extends Array {
// Add stack-specific methods
peek() {
return this[this.length - 1];
}
isEmpty() {
return this.length === 0;
}
// Override to return Stack instead of Array
static get [Symbol.species]() {
return Array;
}
}
const stack = new Stack();
stack.push(1, 2, 3);
console.log(stack.peek()); // 3
console.log(stack.pop()); // 3
console.log(stack.isEmpty()); // false
console.log(stack instanceof Stack); // true
console.log(stack instanceof Array); // true
// map returns Array (due to Symbol.species)
const mapped = stack.map((x) => x * 2);
console.log(mapped instanceof Stack); // false
console.log(mapped instanceof Array); // true
Extending Error
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
// Maintains proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ValidationError);
}
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = 'DatabaseError';
this.query = query;
}
}
function validateEmail(email) {
if (!email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
}
try {
validateEmail('invalid-email');
} catch (e) {
if (e instanceof ValidationError) {
console.log(`${e.name}: ${e.message} (field: ${e.field})`);
}
}
Extending Map
class DefaultMap extends Map {
constructor(defaultValue, entries) {
super(entries);
this.defaultValue = defaultValue;
}
get(key) {
if (!this.has(key)) {
return typeof this.defaultValue === 'function'
? this.defaultValue()
: this.defaultValue;
}
return super.get(key);
}
}
const counts = new DefaultMap(0);
counts.set('a', 1);
console.log(counts.get('a')); // 1
console.log(counts.get('b')); // 0 (default)
const lists = new DefaultMap(() => []);
lists.get('users').push('Alice');
lists.get('users').push('Bob');
console.log(lists.get('users')); // ["Alice", "Bob"]
The instanceof Operator
Checks if an object is an instance of a class or its parents.
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
const dog = new Dog();
const cat = new Cat();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Cat); // false
console.log(dog instanceof Object); // true
// Works with constructor functions too
function Bird() {}
const bird = new Bird();
console.log(bird instanceof Bird); // true
Custom instanceof Behavior
class MyClass {
static [Symbol.hasInstance](instance) {
// Custom logic
return instance && instance.hasOwnProperty('customProp');
}
}
const obj1 = { customProp: true };
const obj2 = { otherProp: false };
console.log(obj1 instanceof MyClass); // true
console.log(obj2 instanceof MyClass); // false
Abstract Base Classes
JavaScript doesn't have abstract classes, but you can simulate them.
class AbstractShape {
constructor() {
if (new.target === AbstractShape) {
throw new Error('AbstractShape cannot be instantiated directly');
}
}
// Abstract method
area() {
throw new Error("Method 'area()' must be implemented");
}
// Abstract method
perimeter() {
throw new Error("Method 'perimeter()' must be implemented");
}
// Concrete method
describe() {
return `Shape with area ${this.area()} and perimeter ${this.perimeter()}`;
}
}
class Circle extends AbstractShape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
perimeter() {
return 2 * Math.PI * this.radius;
}
}
// const shape = new AbstractShape(); // Error!
const circle = new Circle(5);
console.log(circle.describe());
Interface-like Pattern
class Serializable {
serialize() {
throw new Error('serialize() must be implemented');
}
static deserialize(data) {
throw new Error('deserialize() must be implemented');
}
}
class User extends Serializable {
constructor(name, email) {
super();
this.name = name;
this.email = email;
}
serialize() {
return JSON.stringify({ name: this.name, email: this.email });
}
static deserialize(data) {
const { name, email } = JSON.parse(data);
return new User(name, email);
}
}
Multi-Level Inheritance
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Multi-Level Inheritance β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β class LivingThing { breathe() } β
β β² β
β β β
β class Animal extends LivingThing { move() } β
β β² β
β β β
β class Mammal extends Animal { nurse() } β
β β² β
β β β
β class Dog extends Mammal { bark() } β
β β
β Dog has: bark(), nurse(), move(), breathe() β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class LivingThing {
breathe() {
console.log(`${this.name} is breathing`);
}
}
class Animal extends LivingThing {
constructor(name) {
super();
this.name = name;
}
move() {
console.log(`${this.name} is moving`);
}
}
class Mammal extends Animal {
nurse() {
console.log(`${this.name} is nursing`);
}
}
class Dog extends Mammal {
bark() {
console.log(`${this.name} barks: Woof!`);
}
}
const dog = new Dog('Rex');
dog.breathe(); // From LivingThing
dog.move(); // From Animal
dog.nurse(); // From Mammal
dog.bark(); // From Dog
Best Practices
1. Favor Composition Over Deep Inheritance
// Avoid: Deep inheritance hierarchies
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class E extends D {} // Hard to maintain!
// Better: Composition
class Component {
constructor() {
this.logger = new Logger();
this.validator = new Validator();
}
}
2. Call super() First in Constructors
class Child extends Parent {
constructor(args) {
// Always first!
super(args);
// Then your code
this.childProp = value;
}
}
3. Don't Override for No Reason
// Bad: Unnecessary override
class Dog extends Animal {
speak() {
super.speak(); // Just calls parent
}
}
// Good: Only override when adding behavior
class Dog extends Animal {
speak() {
super.speak();
console.log('Woof!'); // Adds behavior
}
}
Summary
| Concept | Description |
|---|---|
extends | Creates child class from parent |
super() | Calls parent constructor |
super.method() | Calls parent method |
| Method override | Replace parent method in child |
instanceof | Check if object is instance of class |
new.target | Detect if class is instantiated directly |
Next Steps
- β’Learn about static properties and methods
- β’Explore private and public class fields
- β’Study mixins for horizontal code reuse
- β’Practice with advanced class patterns