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:
- Code Reuse: Avoid writing the same code repeatedly
- Hierarchical Design: Establish a clear class hierarchy
- Extensibility: Extend functionality without modifying the original code
- 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()); // 952. 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:
- Basic Syntax: Use
extendskeyword for inheritance,superto call parent class - Constructor: Child class constructor must call
super()before usingthis - Method Override: Child classes can override parent methods, use
superto call parent methods - Multi-level Inheritance: Supports multiple levels of inheritance hierarchy
- Static Inheritance: Child classes inherit parent's static methods and properties
- Polymorphism: Same interface can have different implementations
- Best Practices: Design inheritance hierarchies reasonably, prefer composition over inheritance
- 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.