Skip to content

JavaScript Class Inheritance

Inheritance is one of the core concepts of object-oriented programming, allowing us to create new classes that extend the functionality of existing classes. JavaScript supports class inheritance through the extends keyword, making code reuse and hierarchical design easier. In this chapter, we'll learn the class inheritance mechanism in JavaScript.

What is Inheritance

Inheritance allows one class (subclass) to acquire the properties and methods of another class (parent class). This mechanism provides the following advantages:

  1. Code Reuse: Avoid writing the same code repeatedly
  2. Hierarchical Design: Establish a clear class hierarchy
  3. Extensibility: Extend functionality without modifying the original code
  4. Polymorphism: The same interface can have different implementations

Basic Inheritance Syntax

The extends Keyword

javascript
// Parent class (base class)
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
  
  move() {
    return `${this.name} is moving`;
  }
  
  getInfo() {
    return `I am a ${this.species} named ${this.name}`;
  }
}

// Child class (derived class)
class Dog extends Animal {
  constructor(name, breed) {
    super(name, "dog"); // Call parent constructor
    this.breed = breed;
  }
  
  // Override parent method
  speak() {
    return `${this.name} barks`;
  }
  
  // Child-specific method
  fetch() {
    return `${this.name} fetches the ball`;
  }
  
  // Extend parent method
  getInfo() {
    return `${super.getInfo()}, breed is ${this.breed}`;
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(name, "cat");
    this.color = color;
  }
  
  speak() {
    return `${this.name} meows`;
  }
  
  climb() {
    return `${this.name} climbs a tree`;
  }
  
  getInfo() {
    return `${super.getInfo()}, fur color is ${this.color}`;
  }
}

// Usage examples
const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "orange");

console.log(dog.speak()); // "Buddy barks"
console.log(dog.move()); // "Buddy is moving" (inherited from parent)
console.log(dog.fetch()); // "Buddy fetches the ball"
console.log(dog.getInfo()); // "I am a dog named Buddy, breed is Golden Retriever"

console.log(cat.speak()); // "Whiskers meows"
console.log(cat.climb()); // "Whiskers climbs a tree"
console.log(cat.getInfo()); // "I am a cat named Whiskers, fur color is orange"

The super Keyword

Calling Parent Constructor

javascript
class Vehicle {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
    this.isRunning = false;
  }
  
  start() {
    this.isRunning = true;
    return `${this.brand} ${this.model} started`;
  }
  
  stop() {
    this.isRunning = false;
    return `${this.brand} ${this.model} stopped`;
  }
  
  getInfo() {
    return `${this.year} ${this.brand} ${this.model}`;
  }
}

class Car extends Vehicle {
  constructor(brand, model, year, doors) {
    super(brand, model, year); // Must be called before using this
    this.doors = doors;
    this.fuel = 100;
  }
  
  drive(distance) {
    if (!this.isRunning) {
      return "Please start the vehicle first";
    }
    
    const fuelConsumed = distance * 0.1;
    if (this.fuel < fuelConsumed) {
      return "Insufficient fuel";
    }
    
    this.fuel -= fuelConsumed;
    return `Drove ${distance} km, fuel remaining ${this.fuel.toFixed(1)}%`;
  }
  
  refuel(amount) {
    this.fuel = Math.min(100, this.fuel + amount);
    return `Refueled ${amount}%, current fuel ${this.fuel}%`;
  }
  
  getInfo() {
    return `${super.getInfo()} (${this.doors}-door)`;
  }
}

// Usage example
const car = new Car("Toyota", "Corolla", 2023, 4);
console.log(car.start()); // "Toyota Corolla started"
console.log(car.drive(50)); // "Drove 50 km, fuel remaining 95.0%"
console.log(car.getInfo()); // "2023 Toyota Corolla (4-door)"

Calling Parent Methods

javascript
class Shape {
  constructor(color) {
    this.color = color;
  }
  
  getArea() {
    return 0;
  }
  
  getPerimeter() {
    return 0;
  }
  
  getDescription() {
    return `This is a ${this.color} shape`;
  }
  
  getInfo() {
    return {
      type: this.constructor.name,
      color: this.color,
      area: this.getArea(),
      perimeter: this.getPerimeter()
    };
  }
}

class Rectangle extends Shape {
  constructor(color, width, height) {
    super(color);
    this.width = width;
    this.height = height;
  }
  
  getArea() {
    return this.width * this.height;
  }
  
  getPerimeter() {
    return 2 * (this.width + this.height);
  }
  
  getDescription() {
    return `${super.getDescription()}, size is ${this.width}x${this.height}`;
  }
  
  isSquare() {
    return this.width === this.height;
  }
}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);
    this.radius = radius;
  }
  
  getArea() {
    return Math.PI * this.radius * this.radius;
  }
  
  getPerimeter() {
    return 2 * Math.PI * this.radius;
  }
  
  getDescription() {
    return `${super.getDescription()}, radius is ${this.radius}`;
  }
  
  getDiameter() {
    return this.radius * 2;
  }
}

// Usage examples
const rectangle = new Rectangle("red", 10, 5);
const circle = new Circle("blue", 3);

console.log(rectangle.getDescription()); 
// "This is a red shape, size is 10x5"
console.log(rectangle.getInfo());
// { type: "Rectangle", color: "red", area: 50, perimeter: 30 }

console.log(circle.getDescription()); 
// "This is a blue shape, radius is 3"
console.log(circle.getInfo());
// { type: "Circle", color: "blue", area: 28.274..., perimeter: 18.849... }

Multi-level Inheritance

javascript
// Base class
class Animal {
  constructor(name) {
    this.name = name;
    this.energy = 100;
  }
  
  eat(food) {
    this.energy += 10;
    return `${this.name} ate ${food}, energy increased to ${this.energy}`;
  }
  
  sleep() {
    this.energy = 100;
    return `${this.name} slept, energy restored to ${this.energy}`;
  }
  
  getInfo() {
    return `${this.name} (Energy: ${this.energy})`;
  }
}

// First level inheritance
class Mammal extends Animal {
  constructor(name, furColor) {
    super(name);
    this.furColor = furColor;
    this.bodyTemperature = 37;
  }
  
  regulateTemperature() {
    return `${this.name} regulates body temperature to ${this.bodyTemperature}°C`;
  }
  
  getInfo() {
    return `${super.getInfo()} [Mammal, Fur: ${this.furColor}]`;
  }
}

// Second level inheritance
class Dog extends Mammal {
  constructor(name, furColor, breed) {
    super(name, furColor);
    this.breed = breed;
    this.tricks = [];
  }
  
  bark() {
    this.energy -= 5;
    return `${this.name} barks (Energy: ${this.energy})`;
  }
  
  learnTrick(trick) {
    this.tricks.push(trick);
    return `${this.name} learned ${trick}`;
  }
  
  performTrick() {
    if (this.tricks.length === 0) {
      return `${this.name} hasn't learned any tricks yet`;
    }
    
    if (this.energy < 20) {
      return `${this.name} is too tired, needs rest`;
    }
    
    const trick = this.tricks[Math.floor(Math.random() * this.tricks.length)];
    this.energy -= 20;
    return `${this.name} performed ${trick} (Energy: ${this.energy})`;
  }
  
  getInfo() {
    return `${super.getInfo()} [Breed: ${this.breed}, Tricks: ${this.tricks.join(", ")}]`;
  }
}

// Usage examples
const dog = new Dog("Buddy", "golden", "Golden Retriever");

console.log(dog.eat("dog food")); // "Buddy ate dog food, energy increased to 110"
console.log(dog.bark()); // "Buddy barks (Energy: 105)"
console.log(dog.learnTrick("shake hands")); // "Buddy learned shake hands"
console.log(dog.performTrick()); // "Buddy performed shake hands (Energy: 85)"
console.log(dog.getInfo());

Method Override and Polymorphism

javascript
class PaymentMethod {
  constructor(name) {
    this.name = name;
  }
  
  processPayment(amount) {
    return `Paying $${amount} using ${this.name}`;
  }
  
  validate() {
    return true;
  }
  
  getFee(amount) {
    return 0;
  }
}

class CreditCard extends PaymentMethod {
  constructor(name, cardNumber) {
    super(name);
    this.cardNumber = cardNumber;
    this.limit = 50000;
    this.balance = 0;
  }
  
  // Override parent method
  processPayment(amount) {
    if (!this.validate()) {
      throw new Error("Payment validation failed");
    }
    
    const totalAmount = amount + this.getFee(amount);
    
    if (this.balance + totalAmount > this.limit) {
      throw new Error("Credit limit exceeded");
    }
    
    this.balance += totalAmount;
    return `Credit card payment successful: $${amount} (Fee: $${this.getFee(amount)})`;
  }
  
  validate() {
    return this.cardNumber && this.cardNumber.length === 16;
  }
  
  getFee(amount) {
    return amount * 0.02; // 2% fee
  }
  
  getAvailableCredit() {
    return this.limit - this.balance;
  }
}

class PayPal extends PaymentMethod {
  constructor(name, account) {
    super(name);
    this.account = account;
    this.balance = 10000;
  }
  
  processPayment(amount) {
    if (!this.validate()) {
      throw new Error("Payment validation failed");
    }
    
    const totalAmount = amount + this.getFee(amount);
    
    if (this.balance < totalAmount) {
      throw new Error("Insufficient balance");
    }
    
    this.balance -= totalAmount;
    return `PayPal payment successful: $${amount} (Fee: $${this.getFee(amount)})`;
  }
  
  validate() {
    return this.account && this.account.includes("@");
  }
  
  getFee(amount) {
    return amount > 2000 ? amount * 0.001 : 0;
  }
  
  deposit(amount) {
    this.balance += amount;
    return `Deposited $${amount}, current balance: $${this.balance}`;
  }
}

// Polymorphism: Same interface, different implementations
const creditCard = new CreditCard("Visa", "1234567890123456");
const paypal = new PayPal("PayPal", "user@example.com");

const payments = [creditCard, paypal];

payments.forEach(payment => {
  try {
    console.log(payment.processPayment(1000));
  } catch (error) {
    console.log(`${payment.name}: ${error.message}`);
  }
});

Static Methods in Inheritance

javascript
class Database {
  constructor(name) {
    this.name = name;
    this.isConnected = false;
  }
  
  connect() {
    this.isConnected = true;
    return `Connected to database ${this.name}`;
  }
  
  disconnect() {
    this.isConnected = false;
    return `Disconnected from database ${this.name}`;
  }
  
  // Static method
  static getDefaultConfig() {
    return {
      host: "localhost",
      port: 3306,
      timeout: 5000
    };
  }
  
  static validateConfig(config) {
    return config.host && config.port;
  }
  
  // Static property (ES2022)
  static VERSION = "1.0.0";
}

class MySQL extends Database {
  constructor(name, config) {
    super(name);
    this.config = { ...Database.getDefaultConfig(), ...config };
  }
  
  connect() {
    if (!Database.validateConfig(this.config)) {
      throw new Error("Invalid database configuration");
    }
    
    return `${super.connect()} [MySQL ${MySQL.VERSION}]`;
  }
  
  query(sql) {
    if (!this.isConnected) {
      throw new Error("Database not connected");
    }
    
    return `Executing query: ${sql}`;
  }
  
  // Override static method
  static getDefaultConfig() {
    return {
      ...super.getDefaultConfig(),
      engine: "InnoDB",
      charset: "utf8mb4"
    };
  }
}

// Usage examples
console.log("Database version:", Database.VERSION); // "1.0.0"

const mysqlConfig = {
  host: "localhost",
  port: 3306,
  engine: "InnoDB"
};

const mysql = new MySQL("myapp", mysqlConfig);

console.log(mysql.connect()); // "Connected to database myapp [MySQL 1.0.0]"
console.log(mysql.query("SELECT * FROM users"));

// Static method call
console.log("MySQL default config:", MySQL.getDefaultConfig());

Inheritance Best Practices

1. Composition Over Inheritance

javascript
// Composition approach (more flexible)
class Device {
  constructor(brand, model) {
    this.brand = brand;
    this.model = model;
  }
  
  powerOn() {
    return `${this.brand} ${this.model} powered on`;
  }
  
  powerOff() {
    return `${this.brand} ${this.model} powered off`;
  }
}

class Battery {
  constructor(capacity = 100) {
    this.capacity = capacity;
    this.level = capacity;
  }
  
  use(amount) {
    this.level = Math.max(0, this.level - amount);
  }
  
  charge() {
    this.level = this.capacity;
  }
  
  getLevel() {
    return this.level;
  }
  
  isLow() {
    return this.level < 20;
  }
}

class AppManager {
  constructor() {
    this.installedApps = [];
  }
  
  install(app) {
    this.installedApps.push(app);
    return `Installed app: ${app}`;
  }
  
  uninstall(app) {
    this.installedApps = this.installedApps.filter(a => a !== app);
    return `Uninstalled app: ${app}`;
  }
  
  getInstalledApps() {
    return [...this.installedApps];
  }
}

class SmartPhone {
  constructor(brand, model) {
    this.device = new Device(brand, model);
    this.battery = new Battery(100);
    this.appManager = new AppManager();
  }
  
  powerOn() {
    return this.device.powerOn();
  }
  
  installApp(app) {
    return this.appManager.install(app);
  }
  
  runApp(app) {
    if (this.battery.isLow()) {
      return "Low battery, please charge";
    }
    
    this.battery.use(5);
    return `Running app: ${app}`;
  }
  
  charge() {
    this.battery.charge();
    return "Charging complete";
  }
  
  getBatteryLevel() {
    return this.battery.getLevel();
  }
}

// Usage example
const phone = new SmartPhone("Apple", "iPhone 15");

console.log(phone.powerOn()); // "Apple iPhone 15 powered on"
console.log(phone.installApp("WhatsApp")); // "Installed app: WhatsApp"
console.log(phone.runApp("WhatsApp")); // "Running app: WhatsApp"
console.log(phone.getBatteryLevel()); // 95

2. Abstract Class Simulation

javascript
// Abstract class (simulated)
class Animal {
  constructor(name) {
    if (this.constructor === Animal) {
      throw new Error("Cannot instantiate abstract class Animal");
    }
    this.name = name;
  }
  
  // Abstract method (must be implemented by subclasses)
  makeSound() {
    throw new Error("Abstract method makeSound must be implemented by subclass");
  }
  
  // Concrete method
  eat() {
    return `${this.name} is eating`;
  }
  
  sleep() {
    return `${this.name} is sleeping`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  
  // Implement abstract method
  makeSound() {
    return `${this.name} barks`;
  }
  
  // Dog-specific method
  fetch() {
    return `${this.name} fetches the ball`;
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
  }
  
  // Implement abstract method
  makeSound() {
    return `${this.name} meows`;
  }
  
  // Cat-specific method
  climb() {
    return `${this.name} climbs a tree`;
  }
}

// Usage examples
try {
  // const animal = new Animal("Test"); // Error: Cannot instantiate abstract class
} catch (error) {
  console.log(error.message);
}

const dog = new Dog("Buddy");
const cat = new Cat("Whiskers");

console.log(dog.makeSound()); // "Buddy barks"
console.log(dog.fetch()); // "Buddy fetches the ball"

console.log(cat.makeSound()); // "Whiskers meows"
console.log(cat.climb()); // "Whiskers climbs a tree"

Practical Example: Game Character System

javascript
// Base character class
class Character {
  constructor(name, level = 1) {
    this.name = name;
    this.level = level;
    this.health = 100;
    this.maxHealth = 100;
    this.mana = 50;
    this.maxMana = 50;
    this.experience = 0;
    this.experienceToNextLevel = 100;
  }
  
  takeDamage(damage) {
    this.health = Math.max(0, this.health - damage);
    return `${this.name} took ${damage} damage, health: ${this.health}`;
  }
  
  heal(amount) {
    this.health = Math.min(this.maxHealth, this.health + amount);
    return `${this.name} healed ${amount} HP`;
  }
  
  useMana(amount) {
    if (this.mana >= amount) {
      this.mana -= amount;
      return true;
    }
    return false;
  }
  
  gainExperience(exp) {
    this.experience += exp;
    
    if (this.experience >= this.experienceToNextLevel) {
      return this.levelUp();
    }
    
    return `${this.name} gained ${exp} experience`;
  }
  
  levelUp() {
    this.level++;
    this.experience -= this.experienceToNextLevel;
    this.experienceToNextLevel = Math.floor(this.experienceToNextLevel * 1.5);
    
    this.maxHealth += 20;
    this.health = this.maxHealth;
    this.maxMana += 10;
    this.mana = this.maxMana;
    
    return `${this.name} leveled up to level ${this.level}!`;
  }
  
  isAlive() {
    return this.health > 0;
  }
  
  getInfo() {
    return {
      name: this.name,
      level: this.level,
      health: this.health,
      maxHealth: this.maxHealth,
      mana: this.mana,
      maxMana: this.maxMana
    };
  }
}

// Warrior class
class Warrior extends Character {
  constructor(name) {
    super(name);
    this.strength = 20;
    this.armor = 5;
  }
  
  attack(target) {
    const damage = this.strength - target.armor;
    return target.takeDamage(Math.max(1, damage));
  }
  
  heavyStrike(target) {
    const manaCost = 10;
    
    if (!this.useMana(manaCost)) {
      return `${this.name} has insufficient mana`;
    }
    
    const damage = (this.strength * 2) - target.armor;
    return target.takeDamage(Math.max(1, damage));
  }
  
  defend() {
    this.armor += 3;
    return `${this.name} entered defensive stance, armor increased`;
  }
  
  levelUp() {
    const result = super.levelUp();
    this.strength += 5;
    this.armor += 2;
    return result;
  }
  
  getInfo() {
    return {
      ...super.getInfo(),
      class: "Warrior",
      strength: this.strength,
      armor: this.armor
    };
  }
}

// Mage class
class Mage extends Character {
  constructor(name) {
    super(name);
    this.intelligence = 25;
    this.spellPower = 15;
  }
  
  fireball(target) {
    const manaCost = 15;
    
    if (!this.useMana(manaCost)) {
      return `${this.name} has insufficient mana`;
    }
    
    const damage = this.spellPower + (this.intelligence * 0.5);
    return target.takeDamage(damage);
  }
  
  healSpell(target) {
    const manaCost = 20;
    
    if (!this.useMana(manaCost)) {
      return `${this.name} has insufficient mana`;
    }
    
    const healAmount = this.intelligence;
    return target.heal(healAmount);
  }
  
  levelUp() {
    const result = super.levelUp();
    this.intelligence += 7;
    this.spellPower += 3;
    return result;
  }
  
  getInfo() {
    return {
      ...super.getInfo(),
      class: "Mage",
      intelligence: this.intelligence,
      spellPower: this.spellPower
    };
  }
}

// Monster class
class Monster extends Character {
  constructor(name, level, type) {
    super(name, level);
    this.type = type;
    this.expReward = level * 20;
    this.goldReward = level * 10;
    
    // Adjust stats based on level
    this.maxHealth = 50 + (level * 25);
    this.health = this.maxHealth;
    this.strength = 10 + (level * 3);
    this.armor = level;
  }
  
  attack(target) {
    const damage = this.strength - target.armor;
    return target.takeDamage(Math.max(1, damage));
  }
  
  getInfo() {
    return {
      ...super.getInfo(),
      type: this.type,
      expReward: this.expReward,
      goldReward: this.goldReward
    };
  }
}

// Usage examples
const warrior = new Warrior("John");
const mage = new Mage("Alice");

const goblin = new Monster("Goblin", 1, "Normal");
const orc = new Monster("Orc", 3, "Elite");

// Battle example
console.log(warrior.attack(goblin));
console.log(mage.fireball(goblin));

if (!goblin.isAlive()) {
  console.log(warrior.gainExperience(goblin.expReward));
}

console.log(orc.attack(warrior));
console.log(warrior.heavyStrike(orc));
console.log(mage.healSpell(warrior));

console.log("Warrior info:", warrior.getInfo());
console.log("Mage info:", mage.getInfo());

Summary

Key points of JavaScript class inheritance:

  1. Basic Syntax: Use extends keyword for inheritance, super to call parent class
  2. Constructor: Child class constructor must call super() before using this
  3. Method Override: Child classes can override parent methods, use super to call parent methods
  4. Multi-level Inheritance: Supports multiple levels of inheritance hierarchy
  5. Static Inheritance: Child classes inherit parent's static methods and properties
  6. Polymorphism: Same interface can have different implementations
  7. Best Practices: Design inheritance hierarchies reasonably, prefer composition over inheritance
  8. Abstract Classes: Simulate abstract classes and methods by throwing errors

Mastering class inheritance is an important skill in object-oriented programming. In the next chapter, we'll learn about JavaScript static methods.

Content is for learning and research only.