Docs

README

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

  1. β€’The extends Keyword
  2. β€’The super Keyword
  3. β€’Method Overriding
  4. β€’Constructor Inheritance
  5. β€’Prototype Chain with Classes
  6. β€’Extending Built-in Classes
  7. β€’The instanceof Operator
  8. β€’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

InheritedNot 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

RuleDescription
Must call in constructorIf you define a constructor in child class
Before thissuper() must be called before accessing this
Only in derived classCan't use super in non-extending class
Can access parent methodssuper.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

ConceptDescription
extendsCreates child class from parent
super()Calls parent constructor
super.method()Calls parent method
Method overrideReplace parent method in child
instanceofCheck if object is instance of class
new.targetDetect 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
README - JavaScript Tutorial | DeepML