JavaScript Mastery: From Fundamentals to Modern ES2024+
HomeInsightsCoursesJavaScriptInheritance Patterns
Architecture

Inheritance & Composition

In JavaScript, inheritance is not about copying code; it is aboutdelegation. Whether you use deep prototype chains or flat functional composition, mastering how objects share behavior is the key to scalable architecture.

Classical Prototypal Inheritance

Before ES6 classes, inheritance was achieved throughConstructor Stealing (using call) and Prototype Linking. Small mistakes here, like forgetting to reset the constructor property, can lead to broken instance tracking in complex apps.

JAVASCRIPT
// --- Base Component ---
function Component(id) {
    this.id = id;
}
Component.prototype.render = function() {
    console.log(`Rendering [${this.id}]`);
};

// --- Child Component (Constructor Stealing) ---
function Button(id, label) {
    // 1. "Steal" the parent constructor's properties
    Component.call(this, id); 
    this.label = label;
}

// 2. Link the Prototype Chain
Button.prototype = Object.create(Component.prototype);
Button.prototype.constructor = Button;

// 3. Extension (Method Override)
Button.prototype.render = function() {
    Component.prototype.render.call(this); // Super-like call
    console.log(`Label: ${this.label}`);
};

The Prototype Chain Mechanics

The prototype chain is a linked list of objects. When you access a property, the engine starts at the instance and climbs the[[Prototype]] links until it finds a match or hitsnull. Understanding this lookup process is vital for optimizing performance in hot code paths.

JAVASCRIPT
// --- Pure Prototypal Chain ---
const machine = {
    powerOn() { this.active = true; }
};

const robot = Object.create(machine);
robot.task = 'Cleanup';

const vacuum = Object.create(robot);

// JavaScript digs through the chain:
// vacuum.active? -> No
// robot.active?  -> No
// machine.active? -> Found!
vacuum.powerOn(); 
console.log(vacuum.active); // true

Composition: Favoring "Has-A" Over "Is-A"

Deep inheritance hierarchies often lead to the "Gorilla/Banana Problem" (you wanted a banana, but got a gorilla holding the banana and the entire jungle). Compositionallows you to build objects by combining small, specific behaviors rather than rigid taxonomies.

JAVASCRIPT
// --- Composition (The "Has-A" Strategy) ---
const canFly = (state) => ({
    fly: () => console.log(`${state.name} is soaring!`)
});

const canAttack = (state) => ({
    attack: () => console.log(`${state.name} strikes for ${state.dmg}!`)
});

const createDragon = (name) => {
    const state = { name, dmg: 50 };
    // Combine behaviors into a new object
    return Object.assign(state, canFly(state), canAttack(state));
};

const smaug = createDragon('Smaug');
smaug.fly();
smaug.attack();
💡 Pro Tip: Use inheritance (Classes) for UI components where a clear hierarchy exists, but use composition (Mixins/Hooks) for logic and state management to maximize reusability.

Senior Architect's Checklist:

  • ✅ Delegation: Prefer prototypes for shared methods to save memory (one function reference for many instances).
  • ✅ Flatness: Keep inheritance chains shallow (2-3 levels max) to avoid performance degradation.
  • ❌ Tight Coupling: Avoid base classes that "know" too much about their children.
  • ✅ Identity: Always verify instanceof and constructor references after prototype linking.
  • ✅ Composition: Use Object.assign or spread for simple behavior merging.

What's Next?

Inheritance is powerful, but how do we keep internal data safe? Let's explore the final frontier of JS encapsulation: Private Fields.