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