Labs ICT
Pro Login

Inheritance

Inheritance is a fundamental concept in object-oriented programming that allows objects to inherit properties and methods from other objects. JavaScript uses prototype-based inheritance, which provides powerful and flexible ways to create object hierarchies.

In this comprehensive tutorial, we'll explore JavaScript's inheritance mechanisms, from prototype-based inheritance to ES6 classes, and learn how to implement effective inheritance patterns in your applications.

Prototype-Based Inheritance

Understanding JavaScript's prototype chain and how it enables inheritance in JavaScript.

The Prototype Chain

// Understanding the prototype chain
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  return `Hello, ${this.name}!`;
};

// Creating an object with new
const john = new Person('John');
console.log(john.greet()); // "Hello, John!"

// Checking the prototype chain
console.log(john.__proto__ === Person.prototype); // true
console.log(john.__proto__.__proto__ === Object.prototype); // true

// The prototype chain lookup
console.log(john.toString()); // Calls Object.prototype.toString
console.log(john.hasOwnProperty('name')); // true (own property)
console.log(john.hasOwnProperty('greet')); // false (inherited property)

// hasOwnProperty vs in operator
console.log('name' in john); // true (own + inherited)
console.log('greet' in john); // true (inherited)
console.log('toString' in john); // true (inherited from Object)

// getOwnPropertyNames vs Object.keys
console.log(john.getOwnPropertyNames()); // ['name']
console.log(Object.keys(john)); // ['name']
console.log(Object.getOwnPropertyDescriptors(john)); // { name: { value: 'John', ... } }

// Property descriptor
const descriptor = Object.getOwnPropertyDescriptor(john, 'name');
console.log(descriptor);
// { value: 'John', writable: true, enumerable: true, configurable: true }

Creating Inheritance

// Basic inheritance pattern
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  return `Hello, ${this.name}!`;
};

// Employee inherits from Person
function Employee(name, title) {
  // Call parent constructor
  Person.call(this, name);
  this.title = title;
}

// Set up inheritance
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

// Add employee-specific method
Employee.prototype.introduce = function() {
  return `${this.greet()} I'm a ${this.title}.`;
};

// Using inheritance
const employee = new Employee('John', 'Developer');
console.log(employee.introduce()); // "Hello, John! I'm a Developer."

// Checking inheritance
console.log(employee instanceof Person); // true
console.log(employee instanceof Employee); // true
console.log(employee.__proto__ === Employee.prototype); // true
console.log(Employee.prototype.__proto__ === Person.prototype); // true

// Multiple inheritance levels
function Manager(name, title, department) {
  Employee.call(this, name, title);
  this.department = department;
}

Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;

Manager.prototype.manage = function() {
  return `${this.introduce()} I manage the ${this.department} department.`;
};

const manager = new Manager('Jane', 'Manager', 'IT');
console.log(manager.manage()); // "Hello, Jane! I'm a Manager. I manage the IT department."

Inheritance Patterns

// Constructor stealing pattern
function Parent(name) {
  this.name = name;
}

Parent.prototype.parentMethod = function() {
  return 'Parent method';
};

function Child(name, age) {
  // Steal parent constructor
  Parent.call(this, name);
  this.age = age;
}

// Steal parent prototype
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.childMethod = function() {
  return 'Child method';
};

// Using the pattern
const child = new Child('John', 30);
console.log(child.parentMethod()); // 'Parent method'
console.log(child.childMethod()); // 'Child method'

// Factory function pattern
function createPerson(name) {
  const person = Object.create(Person.prototype);
  Person.call(person, name);
  return person;
}

function createEmployee(name, title) {
  const employee = Object.create(Employee.prototype);
  Employee.call(employee, name, title);
  return employee;
}

// Using factory functions
const person = createPerson('John');
const employee = createEmployee('Jane', 'Developer');

console.log(person.greet()); // "Hello, John!"
console.log(employee.introduce()); // "Hello, Jane! I'm a Developer."

// Parasitic inheritance pattern
function extend(Child, Parent) {
  const F = function() {
    Parent.apply(this, arguments);
  };
  
  F.prototype = Object.create(Parent.prototype);
  F.prototype.constructor = F;
  
  Child.prototype = F.prototype;
  Child.prototype.constructor = Child;
  
  return Child;
}

// Using parasitic inheritance
const ExtendedEmployee = extend(Employee, Person);

const extended = new ExtendedEmployee('Bob', 'Senior Developer');
console.log(extended.greet()); // "Hello, Bob!"
console.log(extended.introduce()); // "Hello, Bob! I'm a Senior Developer."

// Mixin pattern
function mixin(target, source) {
  for (const key in source) {
    if (source.hasOwnProperty(key)) {
      target.prototype[key] = source[key];
    }
  }
  return target;
}

const Timestamped = {
  getTimestamp: function() {
    return this._timestamp || new Date();
  },
  setTimestamp: function() {
    this._timestamp = new Date();
  }
};

// Apply mixin
mixin(Employee, Timestamped);

const employee = new Employee('Alice', 'Designer');
employee.setTimestamp();
console.log(employee.getTimestamp()); // Current timestamp

ES6 Classes and Inheritance

Modern JavaScript classes provide cleaner syntax for inheritance while using the same prototype-based mechanism under the hood.

Class Syntax

// ES6 class syntax
class Person {
  constructor(name) {
    this.name = name;
  }
  
  greet() {
    return `Hello, ${this.name}!`;
  }
}

// Using the class
const person = new Person('John');
console.log(person.greet()); // "Hello, John!"

// Class inheritance with extends
class Employee extends Person {
  constructor(name, title) {
    super(name); // Call parent constructor
    this.title = title;
  }
  
  introduce() {
    return `${this.greet()} I'm a ${this.title}.`;
  }
}

// Using inheritance
const employee = new Employee('Jane', 'Developer');
console.log(employee.introduce()); // "Hello, Jane! I'm a Developer."

// Checking inheritance
console.log(employee instanceof Person); // true
console.log(employee instanceof Employee); // true

// Static properties and methods
class MathUtils {
  static PI = 3.14159;
  
  static add(a, b) {
    return a + b;
  }
  
  multiply(a, b) {
    return a * b;
  }
}

// Using static methods
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 6)); // 24

// Static inheritance
class AdvancedMath extends MathUtils {
  static power(base, exponent) {
    return Math.pow(base, exponent);
  }
  
  static factorial(n) {
    if (n <= 1) return 1;
    return n * this.factorial(n - 1);
  }
}

console.log(AdvancedMath.power(2, 8)); // 256
console.log(AdvancedMath.factorial(5)); // 120

Advanced Class Features

// Private fields (ES2022)
class BankAccount {
  #balance = 0; // Private field
  
  constructor(initialBalance) {
    this.#balance = initialBalance;
  }
  
  deposit(amount) {
    this.#balance += amount;
  }
  
  getBalance() {
    return this.#balance;
  }
  
  #validateAmount(amount) {
    return amount > 0;
  }
}

const account = new BankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
// account.#balance = 0; // Error: Private field

// Private methods
class Counter {
  #count = 0;
  
  increment() {
    this.#count++;
  }
  
  #logCount() {
    console.log(`Count: ${this.#count}`);
  }
  
  getCount() {
    this.#logCount();
    return this.#count;
  }
}

const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // Logs "Count: 2" and returns 2

// Protected fields convention
class Vehicle {
  constructor(make, model) {
    this._make = make; // Convention: underscore for protected
    this._model = model;
  }
  
  _validateMake(make) {
    return make && make.length > 0;
  }
  
  getInfo() {
    if (this._validateMake(this._make)) {
      return `${this._make} ${this._model}`;
    }
    return 'Invalid vehicle';
  }
}

class Car extends Vehicle {
  constructor(make, model, year) {
    super(make, model);
    this.year = year;
  }
  
  getInfo() {
    const baseInfo = super.getInfo();
    return baseInfo ? `${baseInfo} (${this.year})` : 'Invalid car';
  }
}

const car = new Car('Toyota', 'Camry', 2022);
console.log(car.getInfo()); // "Toyota Camry (2022)"

// Getters and setters
class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }
  
  get width() {
    return this._width;
  }
  
  set width(value) {
    if (value > 0) {
      this._width = value;
    }
  }
  
  get height() {
    return this._height;
  }
  
  set height(value) {
    if (value > 0) {
      this._height = value;
    }
  }
  
  get area() {
    return this.width * this.height;
  }
}

const rect = new Rectangle(5, 10);
console.log(rect.area); // 50

rect.width = 8;
console.log(rect.area); // 80

// Computed properties
class Circle {
  constructor(radius) {
    this._radius = radius;
  }
  
  get radius() {
    return this._radius;
  }
  
  set radius(value) {
    if (value > 0) {
      this._radius = value;
    }
  }
  
  get diameter() {
    return this._radius * 2;
  }
  
  get circumference() {
    return 2 * Math.PI * this._radius;
  }
  
  get area() {
    return Math.PI * this._radius * this._radius;
  }
}

const circle = new Circle(5);
console.log(circle.diameter); // 10
console.log(circle.circumference); // 31.41592653589793
console.log(circle.area); // 78.53981633974483

Multiple Inheritance

JavaScript doesn't support true multiple inheritance, but there are patterns to achieve similar functionality.

Mixin Pattern

// Mixin for multiple inheritance
const Timestamped = {
  getTimestamp() {
    return this._timestamp || new Date();
  },
  
  setTimestamp() {
    this._timestamp = new Date();
  }
};

const Validated = {
  isValid() {
    return this._valid !== false;
  },
  
  setValid(valid) {
    this._valid = valid;
  }
};

const Logged = {
  log(message) {
    if (!this._logs) {
      this._logs = [];
    }
    this._logs.push({ message, timestamp: new Date() });
  },
  
  getLogs() {
    return this._logs || [];
  }
};

// Apply mixins to class
function applyMixins(ClassConstructor, ...mixins) {
  mixins.forEach(mixin => {
    Object.assign(ClassConstructor.prototype, mixin);
  });
  return ClassConstructor;
}

// Create class with mixins
class User {
  constructor(name) {
    this.name = name;
    this._valid = true;
    this._logs = [];
  }
}

// Apply mixins
const EnhancedUser = applyMixins(User, Timestamped, Validated, Logged);

const user = new EnhancedUser('John');
user.setTimestamp();
user.setValid(true);
user.log('User created');

console.log(user.getTimestamp()); // Current timestamp
console.log(user.isValid()); // true
console.log(user.getLogs()); // Array with log entries

// Functional mixin approach
const withTimestamp = (BaseClass) => {
  return class extends BaseClass {
    constructor(...args) {
      super(...args);
      this._timestamp = new Date();
    }
    
    getTimestamp() {
      return this._timestamp;
    }
  };
};

const withValidation = (BaseClass) => {
  return class extends BaseClass {
    constructor(...args) {
      super(...args);
      this._valid = true;
    }
    
    isValid() {
      return this._valid;
    }
    
    setValid(valid) {
      this._valid = valid;
    }
  };
};

// Compose multiple mixins
const EnhancedUser2 = withValidation(withTimestamp(User));

const user2 = new EnhancedUser2('Jane');
console.log(user2.getTimestamp()); // Current timestamp
console.log(user2.isValid()); // true

// Mixin with inheritance
class AdminUser extends withTimestamp(User) {
  constructor(name, role) {
    super(name);
    this.role = role;
  }
  
  getRole() {
    return this.role;
  }
}

const admin = new AdminUser('Alice', 'Administrator');
console.log(admin.getTimestamp()); // Current timestamp
console.log(admin.getRole()); // 'Administrator'

Composition over Inheritance

// Composition pattern (favor composition over inheritance)
class Engine {
  constructor(type, horsepower) {
    this.type = type;
    this.horsepower = horsepower;
  }
  
  start() {
    return `${this.type} engine starting...`;
  }
  
  stop() {
    return `${this.type} engine stopping...`;
  }
}

class Transmission {
  constructor(type) {
    this.type = type;
  }
  
  shift(gear) {
    return `Shifting to ${gear} in ${this.type} transmission`;
  }
}

class Car {
  constructor(engine, transmission) {
    this.engine = engine;
    this.transmission = transmission;
  }
  
  start() {
    return this.engine.start();
  }
  
  shift(gear) {
    return this.transmission.shift(gear);
  }
  
  stop() {
    return this.engine.stop();
  }
}

// Using composition
const engine = new Engine('V6', 300);
const transmission = new Transmission('Automatic');
const car = new Car(engine, transmission);

console.log(car.start()); // "V6 engine starting..."
console.log(car.shift('Drive')); // "Shifting to Drive in Automatic transmission"
console.log(car.stop()); // "V6 engine stopping..."

// Decorator pattern for composition
function withWheels(BaseClass) {
  return class extends BaseClass {
    constructor(...args) {
      super(...args);
      this.wheels = 4;
    }
    
    rotate() {
      return 'Wheels rotating';
    }
  };
}

function withEngine(BaseClass) {
  return class extends BaseClass {
    constructor(engine, ...args) {
      super(...args);
      this.engine = engine;
    }
    
    startEngine() {
      return this.engine.start();
    }
  };
}

// Compose multiple decorators
const Vehicle = withEngine(withWheels(class {}));
const vehicle = new Vehicle(new Engine('Electric', 200));

console.log(vehicle.rotate()); // "Wheels rotating"
console.log(vehicle.startEngine()); // "Electric engine starting..."

// Strategy pattern
class PaymentProcessor {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  process(amount) {
    return this.strategy.process(amount);
  }
}

class CreditCardStrategy {
  process(amount) {
    return `Processing $${amount} via credit card`;
  }
}

class PayPalStrategy {
  process(amount) {
    return `Processing $${amount} via PayPal`;
  }
}

// Using strategy pattern
const creditCardProcessor = new PaymentProcessor(new CreditCardStrategy());
const payPalProcessor = new PaymentProcessor(new PayPalStrategy());

console.log(creditCardProcessor.process(100)); // "Processing $100 via credit card"
console.log(payPalProcessor.process(50)); // "Processing $50 via PayPal"

Inheritance Best Practices

Guidelines for writing effective and maintainable inheritance in JavaScript.

Design Principles

// Liskov Substitution Principle (LSP)
class Bird {
  fly() {
    return 'Flying';
  }
}

class Penguin extends Bird {
  // Violates LSP - penguins can't fly
  fly() {
    throw new Error('Penguins cannot fly');
  }
}

// Better approach - use composition
class Bird {
  fly() {
    return 'Flying';
  }
}

class FlyingAbility {
  fly() {
    return 'Flying';
  }
}

class Penguin {
  constructor() {
    this.flyingAbility = null;
  }
  
  setFlyingAbility(ability) {
    this.flyingAbility = ability;
  }
  
  tryToFly() {
    if (this.flyingAbility) {
      return this.flyingAbility.fly();
    }
    return 'Cannot fly';
  }
}

// Favor composition over inheritance
class Shape {
  constructor(color) {
    this.color = color;
  }
  
  draw() {
    return `Drawing ${this.color} shape`;
  }
}

class Circle {
  constructor(radius, color) {
    this.radius = radius;
    this.shape = new Shape(color);
  }
  
  draw() {
    return `${this.shape.draw()} - Circle with radius ${this.radius}`;
  }
}

class Square {
  constructor(side, color) {
    this.side = side;
    this.shape = new Shape(color);
  }
  
  draw() {
    return `${this.shape.draw()} - Square with side ${this.side}`;
  }
}

// Single Responsibility Principle
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  
  // Only handles user data
  getName() {
    return this.name;
  }
  
  getEmail() {
    return this.email;
  }
}

class UserRepository {
  constructor() {
    this.users = [];
  }
  
  // Only handles user storage
  save(user) {
    this.users.push(user);
  }
  
  find(id) {
    return this.users.find(u => u.id === id);
  }
}

class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }
  
  // Only handles user business logic
  createUser(name, email) {
    const user = new User(name, email);
    this.userRepository.save(user);
    return user;
  }
  
  getUser(id) {
    return this.userRepository.find(id);
  }
}

// Open/Closed Principle
class DatabaseConnection {
  constructor() {
    this.connection = null;
  }
  
  // Closed for extension, open for modification
  connect() {
    this.connection = 'Connected to database';
  }
  
  disconnect() {
    this.connection = null;
  }
}

class SQLDatabaseConnection extends DatabaseConnection {
  constructor() {
    super();
  }
  
  // Extends functionality
  executeQuery(query) {
    return `Executing SQL: ${query}`;
  }
}

class NoSQLDatabaseConnection extends DatabaseConnection {
  constructor() {
    super();
  }
  
  // Extends functionality differently
  executeQuery(query) {
    return `Executing NoSQL: ${query}`;
  }
}

Performance Considerations

// Performance implications of inheritance
function testInheritancePerformance() {
  const iterations = 100000;
  
  // Test direct object creation
  console.time('Direct object');
  for (let i = 0; i < iterations; i++) {
    const obj = {
      name: `User ${i}`,
      email: `user${i}@example.com`
    };
  }
  console.timeEnd('Direct object');
  
  // Test prototype-based inheritance
  function User(name, email) {
    this.name = name;
    this.email = email;
  }
  
  User.prototype.getName = function() {
    return this.name;
  };
  
  console.time('Prototype inheritance');
  for (let i = 0; i < iterations; i++) {
    const user = new User(`User ${i}`, `user${i}@example.com`);
    user.getName();
  }
  console.timeEnd('Prototype inheritance');
  
  // Test ES6 classes
  class ES6User {
    constructor(name, email) {
      this.name = name;
      this.email = email;
    }
    
    getName() {
      return this.name;
    }
  }
  
  console.time('ES6 class');
  for (let i = 0; i < iterations; i++) {
    const user = new ES6User(`User ${i}`, `user${i}@example.com`);
    user.getName();
  }
  console.timeEnd('ES6 class');
}

testInheritancePerformance();

// Memory efficiency considerations
class MemoryEfficientBase {
  constructor() {
    // Don't create large objects in constructor
    this.data = null;
  }
  
  initialize(data) {
    // Lazy initialization
    this.data = data;
  }
  
  processData() {
    // Process data efficiently
    if (!this.data) return;
    
    let result = 0;
    for (let i = 0; i < this.data.length; i++) {
      result += this.data[i];
    }
    return result;
  }
}

// Avoid deep inheritance chains
class Level1 {
  method() {
    return 'Level 1';
  }
}

class Level2 extends Level1 {
  method() {
    return 'Level 2';
  }
}

class Level3 extends Level2 {
  method() {
    return 'Level 3';
  }
}

class Level4 extends Level3 {
  method() {
    return 'Level 4';
  }
}

class Level5 extends Level4 {
  method() {
    return 'Level 5';
  }
}

// Deep chain can affect performance
const deep = new Level5();
console.log(deep.method()); // 'Level 5'

// Prefer composition for deep hierarchies
class ComposedLevel5 {
  constructor() {
    this.level1 = new Level1();
    this.level2 = new Level2();
    this.level3 = new Level3();
    this.level4 = new Level4();
  }
  
  method() {
    return this.level4.method();
  }
}

const composed = new ComposedLevel5();
console.log(composed.method()); // 'Level 4'

Modern Inheritance Patterns

Modern JavaScript patterns for inheritance using ES6+ features and best practices.

Factory Pattern

// Factory pattern with inheritance
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  
  speak() {
    return `${super.speak()} - Woof!`;
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
  
  speak() {
    return `${super.speak()} - Meow!`;
  }
}

// Factory function
class AnimalFactory {
  static createAnimal(type, ...args) {
    switch (type) {
      case 'dog':
        return new Dog(...args);
      case 'cat':
        return new Cat(...args);
      default:
        return new Animal(...args);
    }
  }
}

// Using the factory
const dog = AnimalFactory.createAnimal('dog', 'Buddy', 'Golden Retriever');
const cat = AnimalFactory.createAnimal('cat', 'Whiskers', 'Tabby');
const generic = AnimalFactory.createAnimal('animal', 'Generic Animal');

console.log(dog.speak()); // "Buddy makes a sound - Woof!"
console.log(cat.speak()); // "Whiskers makes a sound - Meow!"
console.log(generic.speak()); // "Generic Animal makes a sound"

// Abstract factory pattern
class VehicleFactory {
  static createVehicle(type, ...args) {
    const vehicleClasses = {
      car: Car,
      truck: Truck,
      motorcycle: Motorcycle
    };
    
    const VehicleClass = vehicleClasses[type];
    if (!VehicleClass) {
      throw new Error(`Unknown vehicle type: ${type}`);
    }
    
    return new VehicleClass(...args);
  }
}

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
  getInfo() {
    return `${this.make} ${this.model}`;
  }
}

class Truck {
  constructor(make, capacity) {
    this.make = make;
    this.capacity = capacity;
  }
  
  getInfo() {
    return `${this.make} truck with capacity ${this.capacity}`;
  }
}

class Motorcycle {
  constructor(make, type) {
    this.make = make;
    this.type = type;
  }
  
  getInfo() {
    return `${this.make} ${type} motorcycle`;
  }
}

// Using the abstract factory
const car = VehicleFactory.createVehicle('car', 'Toyota', 'Camry');
const truck = VehicleFactory.createVehicle('truck', 'Ford', 5000);
const motorcycle = VehicleFactory.createVehicle('motorcycle', 'Harley', 'Cruiser');

console.log(car.getInfo()); // "Toyota Camry"
console.log(truck.getInfo()); // "Ford truck with capacity 5000"
console.log(motorcycle.getInfo()); // "Harley Cruiser motorcycle"

Observer Pattern

// Observer pattern with inheritance
class Subject {
  constructor() {
    this.observers = [];
  }
  
  subscribe(observer) {
    this.observers.push(observer);
  }
  
  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    // To be implemented by subclasses
  }
}

// Concrete observers
class ConsoleObserver extends Observer {
  update(data) {
    console.log('Console observer:', data);
  }
}

class LoggingObserver extends Observer {
  constructor() {
    super();
    this.logs = [];
  }
  
  update(data) {
    this.logs.push({ data, timestamp: new Date() });
  }
  
  getLogs() {
    return this.logs;
  }
}

class EmailObserver extends Observer {
  constructor(email) {
    super();
    this.email = email;
  }
  
  update(data) {
    // Send email notification
    console.log(`Sending email to ${this.email}:`, data);
  }
}

// Using the observer pattern
const subject = new Subject();
const consoleObserver = new ConsoleObserver();
const loggingObserver = new LoggingObserver();
const emailObserver = new EmailObserver('admin@example.com');

subject.subscribe(consoleObserver);
subject.subscribe(loggingObserver);
subject.subscribe(emailObserver);

// Notify all observers
subject.notify('Data updated at ' + new Date().toISOString());

// Subject with inheritance
class DataStore extends Subject {
  constructor() {
    super();
    this.data = [];
  }
  
  setData(newData) {
    this.data = newData;
    this.notify(this.data);
  }
  
  getData() {
    return this.data;
  }
}

class DataProcessor extends Observer {
  constructor(name) {
    super();
    this.name = name;
    this.processedData = [];
  }
  
  update(data) {
    this.processedData.push({ 
      processor: this.name, 
      data, 
      timestamp: new Date() 
    });
  }
  
  getProcessedData() {
    return this.processedData;
  }
}

// Using inherited observer
const dataStore = new DataStore();
const processor1 = new DataProcessor('Processor 1');
const processor2 = new DataProcessor('Processor 2');

dataStore.subscribe(processor1);
dataStore.subscribe(processor2);

dataStore.setData([1, 2, 3, 4, 5]);

console.log(processor1.getProcessedData());
console.log(processor2.getProcessedData());

Module Pattern

// Module pattern with inheritance
class Module {
  constructor() {
    this.data = {};
  }
  
  set(key, value) {
    this.data[key] = value;
  }
  
  get(key) {
    return this.data[key];
  }
  
  has(key) {
    return key in this.data;
  }
  
  delete(key) {
    delete this.data[key];
  }
}

// Specific modules
class UserModule extends Module {
  constructor() {
    super();
    this.users = {};
  }
  
  addUser(user) {
    this.users[user.id] = user;
  }
  
  getUser(id) {
    return this.users[id];
  }
  
  updateUser(id, updates) {
    const user = this.users[id];
    if (user) {
      Object.assign(user, updates);
    }
  }
  
  deleteUser(id) {
    delete this.users[id];
  }
}

class SettingsModule extends Module {
  constructor() {
    super();
    this.settings = {
      theme: 'light',
      language: 'en',
      notifications: true
    };
  }
  
  updateTheme(theme) {
    this.settings.theme = theme;
    this.notify('theme', theme);
  }
  
  updateLanguage(language) {
    this.settings.language = language;
    this.notify('language', language);
  }
  
  toggleNotifications() {
    this.settings.notifications = !this.settings.notifications;
    this.notify('notifications', this.settings.notifications);
  }
  
  // Notification method to be overridden
  notify(setting, value) {
    // To be implemented by subclasses
  }
}

// Module that combines other modules
class AppModule extends UserModule {
  constructor() {
    super();
    this.settingsModule = new SettingsModule();
    this.setupSettingsListeners();
  }
  
  setupSettingsListeners() {
    // Listen to settings changes
    this.settingsModule.notify = (setting, value) => {
      this.handleSettingsChange(setting, value);
    };
  }
  
  handleSettingsChange(setting, value) {
    console.log(`Setting changed: ${setting} = ${value}`);
    
    // Apply setting changes to users
    if (setting === 'theme') {
      this.applyThemeToAllUsers(value);
    }
  }
  
  applyThemeToAllUsers(theme) {
    Object.values(this.users).forEach(user => {
      user.theme = theme;
    });
  }
}

// Using the module pattern
const app = new AppModule();

app.addUser({ id: 1, name: 'John' });
app.addUser({ id: 2, name: 'Jane' });

app.settingsModule.updateTheme('dark');
app.settingsModule.updateLanguage('es');
app.settingsModule.toggleNotifications();

Inheritance Anti-Patterns

Common pitfalls and anti-patterns to avoid when working with inheritance in JavaScript.

Common Mistakes

// 1. Breaking the prototype chain
function Parent() {
  this.name = 'Parent';
}

function Child() {
  this.name = 'Child';
}

// Wrong: Overwriting prototype
Child.prototype = {}; // Breaks inheritance chain

// Correct: Extending prototype
Child.prototype = Object.create(Parent.prototype);

// 2. Forgetting to call super()
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    // Wrong: Forgetting super()
    this.breed = breed;
  }
  
  // Correct: Call super()
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
}

// 3. Using instance properties instead of prototype
function BadClass() {
  this.method = function() {
    return 'Instance method';
  };
}

function GoodClass() {
  // Wrong: Creating methods in constructor
  constructor() {
    this.method = function() {
      return 'Instance method';
    };
  }
  
  // Correct: Using prototype
}
GoodClass.prototype.method = function() {
  return 'Prototype method';
};

// 4. Creating deep inheritance chains
class Level1 { method() { return 'Level 1'; } }
class Level2 extends Level1 { method() { return 'Level 2'; } }
class Level3 extends Level2 { method() { return 'Level 3'; } }
class Level4 extends Level3 { method() { return 'Level 4'; } }
class Level5 extends Level4 { method() { return 'Level 5'; } }

// Bad: Deep inheritance chain
const deep = new Level5(); // Performance impact

// 5. Violating Liskov Substitution Principle
class Bird {
  fly() {
    return 'Flying';
  }
}

class Ostrich extends Bird {
  fly() {
    throw new Error('Ostriches cannot fly');
  }
}

// Bad: Breaks polymorphism
function makeBirdFly(bird) {
  return bird.fly(); // Fails for ostrich
}

// 6. Using inheritance for code reuse
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  constructor(side) {
    super(side, side); // Good: Reuses logic
  }
}

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  
  getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

// Bad: Using inheritance for unrelated functionality
class CircleWithRectangle extends Circle {
  constructor(radius) {
    super(radius);
    this.rectangleLogic = new Rectangle(1, 1); // Confusing
  }
  
  getArea() {
    return super.getArea() + this.rectangleLogic.getArea();
  }
}

// Good: Using composition
class CircleWithLogger {
  constructor(radius, logger) {
    this.circle = new Circle(radius);
    this.logger = logger;
  }
  
  getArea() {
    const area = this.circle.getArea();
    this.logger.log(`Calculated area: ${area}`);
    return area;
  }
}

Performance Anti-Patterns

// 1. Creating methods in constructors
class BadPerformance {
  constructor() {
    this.method1 = function() { return 'Method 1'; };
    this.method2 = function() { return 'Method 2'; };
    this.method3 = function() { return 'Method 3'; };
  }
}

class GoodPerformance {
  // Methods are shared via prototype
}

GoodPerformance.prototype.method1 = function() { return 'Method 1'; };
GoodPerformance.prototype.method2 = function() { return 'Method 2'; };
GoodPerformance.prototype.method3 = function() { return 'Method 3'; };

// 2. Unnecessary inheritance
class SimpleObject {
  constructor(data) {
    this.data = data;
  }
  
  process() {
    return this.data.processed;
  }
}

// Bad: Inheriting when simple object would work
class ExtendedObject extends SimpleObject {
  constructor(data) {
    super(data);
    this.extension = 'extension';
  }
  
  process() {
    return super.process() + ' with extension';
  }
}

// Good: Using composition
class SimpleObjectWithExtension {
  constructor(data, extension) {
    this.data = data;
    this.extension = extension;
  }
  
  process() {
    return this.data.processed + ' with extension';
  }
}

// 3. Memory leaks in inheritance
class LeakyClass {
  constructor() {
    this.largeArray = new Array(10000).fill(0);
    this.callbacks = [];
  }
  
  addCallback(callback) {
    this.callbacks.push(callback);
  }
  
  // Bad: Not cleaning up callbacks
}

class NonLeakyClass {
  constructor() {
    this.largeArray = new Array(10000).fill(0);
    this.callbacks = [];
  }
  
  addCallback(callback) {
    this.callbacks.push(callback);
  }
  
  cleanup() {
    this.callbacks = [];
    this.largeArray = null;
  }
}

// 4. Overusing inheritance
class Vehicle {
  constructor(make) {
    this.make = make;
  }
  
  start() {
    return `${this.make} starting`;
  }
}

class Car extends Vehicle {
  constructor(make, model) {
    super(make);
    this.model = model;
  }
  
  start() {
    return `${super.start()} - Car ${this.model}`;
  }
}

class ElectricCar extends Car {
  constructor(make, model, battery) {
    super(make, model);
    this.battery = battery;
  }
  
  start() {
    return `${super.start()} - Electric ${this.model} with ${this.battery}% battery`;
  }
}

// Bad: Too many levels
class Level1 extends Vehicle { /* ... */ }
class Level2 extends Level1 { /* ... */ }
class Level3 extends Level2 { /* ... */ }
class Level4 extends Level3 { /* ... */ }
class Level5 extends Level4 { /* ... */ }
class Level6 extends Level5 { /* ... */ }
class Level7 extends Level6 { /* ... */ }
class Level8 extends Level7 { /* ... */ }
class Level9 extends Level8 { /* ... */ }
class Level10 extends Level9 { /* ... */ }

// Good: Favor composition
class VehicleWithFeatures {
  constructor(vehicle, features) {
    this.vehicle = vehicle;
    this.features = features;
  }
  
  start() {
    return this.vehicle.start();
  }
  
  useFeature(feature) {
    return this.features[feature].use();
  }
}

Summary

Key Takeaways

  • JavaScript uses prototype-based inheritance under the hood
  • ES6 classes provide cleaner syntax for inheritance
  • The prototype chain enables property and method lookup
  • Multiple inheritance can be achieved through mixins and composition
  • Private fields and methods are now supported in modern JavaScript
  • Favor composition over inheritance for better flexibility

Best Practices

  • Use ES6 classes for cleaner inheritance syntax
  • Follow SOLID principles for maintainable code
  • Prefer composition over inheritance when appropriate
  • Use mixins for code reuse across different hierarchies
  • Implement proper error handling in inheritance chains
  • Use factory patterns for flexible object creation
  • Consider performance implications of deep inheritance chains
  • Use private fields for encapsulation (ES2022+)
  • Document inheritance relationships and contracts

Common Pitfalls

  • Breaking the prototype chain by overwriting prototype
  • Forgetting to call super() in constructors
  • Creating methods in constructors instead of prototype
  • Creating deep inheritance chains that hurt performance
  • Violating Liskov Substitution Principle
  • Using inheritance when composition would be better
  • Not understanding the difference between __proto__ and prototype
  • Creating memory leaks in inheritance hierarchies
  • Overusing inheritance for simple code reuse
  • Not properly handling edge cases in inheritance