Back to Articles

Chapter 11: The Prototype and Inheritance Saga

July 27, 202514 min read
javascriptprototypesinheritanceclassesoopprototype-chaines6-classes
Chapter 11: The Prototype and Inheritance Saga

Chapter 11: The Prototype and Inheritance Saga

In JavaScript Land, objects can inherit from other objects through a mysterious chain called the prototype chain.

Understanding Prototypes

// Every function has a prototype property
function Hero(name, power) {
    this.name = name;
    this.power = power;
}

// Add methods to the prototype
Hero.prototype.introduce = function() {
    return `I am ${this.name}, and I have ${this.power}!`;
};

Hero.prototype.fight = function(villain) {
    return `${this.name} fights ${villain} using ${this.power}!`;
};

// Create instances
const superman = new Hero("Superman", "super strength");
const batman = new Hero("Batman", "intellect and gadgets");

console.log(superman.introduce()); // "I am Superman, and I have super strength!"
console.log(batman.fight("Joker")); // "Batman fights Joker using intellect and gadgets!"

// All instances share the same methods
console.log(superman.introduce === batman.introduce); // true

// The prototype chain in action
console.log(superman.hasOwnProperty("name"));      // true (own property)
console.log(superman.hasOwnProperty("introduce")); // false (inherited)
console.log("introduce" in superman);              // true (found in chain)

The Prototype Chain

// Every object has a __proto__ property pointing to its prototype
console.log(superman.__proto__ === Hero.prototype);           // true
console.log(Hero.prototype.__proto__ === Object.prototype);   // true
console.log(Object.prototype.__proto__);                      // null (end of chain)

// Property lookup walks up the chain
const obj = {
    name: "Test"
};

console.log(obj.toString()); // Found in Object.prototype
console.log(obj.hasOwnProperty("name")); // Found in Object.prototype
// console.log(obj.nonExistent); // undefined (not found anywhere in chain)

// Prototype pollution (be careful!)
Object.prototype.hackedMethod = function() {
    return "I shouldn't be here!";
};

console.log(superman.hackedMethod()); // "I shouldn't be here!" 
// Every object now has this method!

delete Object.prototype.hackedMethod; // Clean up

Classical Inheritance Patterns

// Constructor inheritance
function Superhero(name, power, secretIdentity) {
    Hero.call(this, name, power); // Call parent constructor
    this.secretIdentity = secretIdentity;
}

// Set up prototype inheritance
Superhero.prototype = Object.create(Hero.prototype);
Superhero.prototype.constructor = Superhero;

// Add specialized methods
Superhero.prototype.revealIdentity = function() {
    return `My secret identity is ${this.secretIdentity}!`;
};

// Override parent method
Superhero.prototype.introduce = function() {
    return Hero.prototype.introduce.call(this) + " I'm a superhero!";
};

const spiderman = new Superhero("Spider-Man", "web-slinging", "Peter Parker");
console.log(spiderman.introduce());     // Calls overridden method
console.log(spiderman.revealIdentity()); // "My secret identity is Peter Parker!"
console.log(spiderman.fight("Green Goblin")); // Inherited from Hero

Modern Class Syntax (ES6+)

// Classes are just syntactic sugar over prototypes
class ModernHero {
    constructor(name, power) {
        this.name = name;
        this.power = power;
    }
    
    introduce() {
        return `I am ${this.name}, and I have ${this.power}!`;
    }
    
    fight(villain) {
        return `${this.name} fights ${villain} using ${this.power}!`;
    }
    
    // Static methods belong to the class, not instances
    static compareHeroes(hero1, hero2) {
        return `${hero1.name} vs ${hero2.name}`;
    }
}

// Inheritance with extends
class ModernSuperhero extends ModernHero {
    constructor(name, power, secretIdentity) {
        super(name, power); // Call parent constructor
        this.secretIdentity = secretIdentity;
    }
    
    revealIdentity() {
        return `My secret identity is ${this.secretIdentity}!`;
    }
    
    // Override parent method
    introduce() {
        return super.introduce() + " I'm a superhero!";
    }
    
    // Getter and setter
    get identity() {
        return this.secretIdentity;
    }
    
    set identity(newIdentity) {
        this.secretIdentity = newIdentity;
    }
}

const modernSpiderman = new ModernSuperhero("Spider-Man", "web-slinging", "Peter Parker");
console.log(modernSpiderman.introduce());
console.log(modernSpiderman.identity); // Using getter
modernSpiderman.identity = "Miles Morales"; // Using setter

// Static method usage
console.log(ModernHero.compareHeroes(superman, modernSpiderman));

Advanced Prototype Patterns

Mixins - Multiple Inheritance Alternative

// JavaScript doesn't support multiple inheritance, but we can use mixins
const CanFly = {
    fly() {
        return `${this.name} is flying!`;
    },
    
    land() {
        return `${this.name} has landed.`;
    }
};

const CanSwim = {
    swim() {
        return `${this.name} is swimming!`;
    },
    
    dive() {
        return `${this.name} dives underwater.`;
    }
};

// Mixin function
function mixin(target, ...sources) {
    Object.assign(target.prototype, ...sources);
    return target;
}

class Aquaman extends Hero {
    constructor(name) {
        super(name, "underwater breathing");
    }
}

// Apply mixins
mixin(Aquaman, CanSwim);

const aquaman = new Aquaman("Aquaman");
console.log(aquaman.swim()); // "Aquaman is swimming!"
console.log(aquaman.introduce()); // Still has Hero methods

Factory Functions with Prototypes

// Alternative to classes - factory functions
function createAnimal(type, name) {
    const animal = Object.create(animalPrototype);
    animal.type = type;
    animal.name = name;
    return animal;
}

const animalPrototype = {
    speak() {
        return `${this.name} the ${this.type} makes a sound.`;
    },
    
    move() {
        return `${this.name} is moving.`;
    }
};

const dog = createAnimal("dog", "Buddy");
console.log(dog.speak()); // "Buddy the dog makes a sound."

Private Properties with WeakMaps

// True privacy using WeakMap
const privateProps = new WeakMap();

class SecureHero {
    constructor(name, secretWeapon) {
        this.name = name;
        privateProps.set(this, { secretWeapon });
    }
    
    useSecretWeapon() {
        const { secretWeapon } = privateProps.get(this);
        return `${this.name} uses ${secretWeapon}!`;
    }
}

const ironman = new SecureHero("Iron Man", "Arc Reactor");
console.log(ironman.useSecretWeapon()); // "Iron Man uses Arc Reactor!"
console.log(ironman.secretWeapon); // undefined - truly private!

Understanding instanceof and Prototype Checks

// instanceof checks the prototype chain
console.log(spiderman instanceof Superhero); // true
console.log(spiderman instanceof Hero);      // true
console.log(spiderman instanceof Object);    // true

// isPrototypeOf checks if object is in prototype chain
console.log(Hero.prototype.isPrototypeOf(spiderman));      // true
console.log(Superhero.prototype.isPrototypeOf(spiderman)); // true

// Object.getPrototypeOf gets the prototype
console.log(Object.getPrototypeOf(spiderman) === Superhero.prototype); // true

// Custom instanceof behavior with Symbol.hasInstance
class SpecialClass {
    static [Symbol.hasInstance](instance) {
        return instance.isSpecial === true;
    }
}

const normalObj = { isSpecial: false };
const specialObj = { isSpecial: true };

console.log(normalObj instanceof SpecialClass); // false
console.log(specialObj instanceof SpecialClass); // true

Prototype Performance Considerations

// Method on prototype (memory efficient)
function EfficientClass() {
    this.data = [];
}

EfficientClass.prototype.processData = function() {
    // This method exists once in memory
    return this.data.map(x => x * 2);
};

// Method in constructor (memory inefficient)
function InefficientClass() {
    this.data = [];
    
    // New function created for each instance!
    this.processData = function() {
        return this.data.map(x => x * 2);
    };
}

// Compare memory usage
const efficient1 = new EfficientClass();
const efficient2 = new EfficientClass();
console.log(efficient1.processData === efficient2.processData); // true (same function)

const inefficient1 = new InefficientClass();
const inefficient2 = new InefficientClass();
console.log(inefficient1.processData === inefficient2.processData); // false (different functions)

Common Prototype Pitfalls

// Pitfall 1: Modifying built-in prototypes
// ❌ Don't do this!
Array.prototype.myCustomMethod = function() {
    return "This pollutes all arrays!";
};

// Pitfall 2: Forgetting to reset constructor
function Parent() {}
function Child() {}

// ❌ Wrong
Child.prototype = Object.create(Parent.prototype);
// Constructor now points to Parent!

// ✅ Correct
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// Pitfall 3: Shared reference properties
function BadDesign() {}
BadDesign.prototype.sharedArray = []; // ❌ All instances share this array!

const bad1 = new BadDesign();
const bad2 = new BadDesign();
bad1.sharedArray.push("oops");
console.log(bad2.sharedArray); // ["oops"] - Unintended sharing!

// ✅ Better approach
function GoodDesign() {
    this.ownArray = []; // Each instance gets its own array
}

ES6+ Class Features

// Private fields (ES2022)
class ModernClass {
    #privateField = 42;
    #privateMethod() {
        return "This is private!";
    }
    
    publicMethod() {
        return this.#privateMethod() + ` Private field: ${this.#privateField}`;
    }
}

const modern = new ModernClass();
console.log(modern.publicMethod()); // Works
// console.log(modern.#privateField); // SyntaxError

// Static blocks for complex initialization
class ComplexClass {
    static #database;
    
    static {
        // Runs once when class is defined
        this.#database = new Map();
        this.#database.set("admin", { role: "superuser" });
    }
    
    static getUser(username) {
        return this.#database.get(username);
    }
}

Next Chapter: Chapter 12: Advanced Concepts and Modern Features

Previous Chapter: Chapter 10: The Asynchronous Adventures

Table of Contents: JavaScript Guide