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.
// --- 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.
// --- 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); // trueComposition: 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.
// --- 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();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
instanceofandconstructorreferences after prototype linking. - ✅ Composition: Use
Object.assignor spread for simple behavior merging.