Module 21: Prototypes and Inheritance
Prototypes are the mechanism by which JavaScript objects inherit features from one another. Understanding prototypes is crucial for mastering JavaScript's object system.
1. Understanding Prototypes
1.1 What is a Prototype?
Every JavaScript object has an internal property called [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf()).
const obj = { name: 'John' };
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // Object.prototype
console.log(obj.__proto__ === Object.prototype); // true
1.2 Prototype Chain
const animal = {
eats: true,
walk() {
console.log('Animal walks');
}
};
const dog = {
barks: true
};
// Set prototype
Object.setPrototypeOf(dog, animal);
console.log(dog.eats); // true (inherited from animal)
console.log(dog.barks); // true (own property)
dog.walk(); // "Animal walks" (inherited method)
// Prototype chain: dog → animal → Object.prototype → null
When accessing a property, JavaScript looks up the prototype chain until it finds the property or reaches null.
2. Constructor Functions and Prototypes
2.1 Constructor Functions
function Person(name, age) {
this.name = name;
this.age = age;
}
// Add methods to prototype
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.birthday = function() {
this.age++;
};
const john = new Person('John', 30);
john.greet(); // "Hello, I'm John"
console.log(john.__proto__ === Person.prototype); // true
2.2 Why Use Prototypes?
// ❌ Bad: Methods in constructor (created for each instance)
function Person(name) {
this.name = name;
this.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
}
const p1 = new Person('John');
const p2 = new Person('Jane');
console.log(p1.greet === p2.greet); // false (different functions!)
// ✅ Good: Methods on prototype (shared by all instances)
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
const p3 = new Person('John');
const p4 = new Person('Jane');
console.log(p3.greet === p4.greet); // true (same function!)
3. Prototype Methods
3.1 Object.create()
const animal = {
eats: true,
walk() {
console.log('Walking...');
}
};
// Create object with animal as prototype
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.eats); // true (inherited)
console.log(rabbit.jumps); // true (own property)
rabbit.walk(); // "Walking..." (inherited)
3.2 Object.getPrototypeOf() / Object.setPrototypeOf()
const animal = { eats: true };
const dog = { barks: true };
// Get prototype
console.log(Object.getPrototypeOf(dog)); // Object.prototype
// Set prototype
Object.setPrototypeOf(dog, animal);
console.log(Object.getPrototypeOf(dog)); // animal
console.log(dog.eats); // true
3.3 hasOwnProperty()
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.hasOwnProperty('jumps')); // true
console.log(rabbit.hasOwnProperty('eats')); // false (inherited)
// Check if property exists (own or inherited)
console.log('eats' in rabbit); // true
console.log('jumps' in rabbit); // true
4. Prototypal Inheritance
4.1 Basic Inheritance
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Inherit from Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
const dog = new Dog('Rex', 'Labrador');
dog.eat(); // "Rex is eating" (inherited)
dog.bark(); // "Rex barks!" (own method)
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
4.2 Multiple Levels of Inheritance
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} eats`);
};
function Mammal(name, furColor) {
Animal.call(this, name);
this.furColor = furColor;
}
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.nurse = function() {
console.log(`${this.name} nurses its young`);
};
function Dog(name, furColor, breed) {
Mammal.call(this, name, furColor);
this.breed = breed;
}
Dog.prototype = Object.create(Mammal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} barks`);
};
const dog = new Dog('Rex', 'brown', 'Labrador');
dog.eat(); // Inherited from Animal
dog.nurse(); // Inherited from Mammal
dog.bark(); // Own method
5. ES6 Classes vs Prototypes
5.1 Class Syntax
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} barks`);
}
}
5.2 Prototype Equivalent
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} barks`);
};
ES6 classes are built on top of prototypes. Understanding prototypes helps you understand what classes are really doing.
6. Prototype Properties
6.1 constructor Property
function Person(name) {
this.name = name;
}
console.log(Person.prototype.constructor === Person); // true
const john = new Person('John');
console.log(john.constructor === Person); // true
// Fixing constructor after inheritance
function Dog(name) {
this.name = name;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Fix constructor reference
6.2 Prototype vs Instance Properties
function Person(name) {
this.name = name; // Instance property
}
Person.prototype.species = 'Human'; // Prototype property
const john = new Person('John');
const jane = new Person('Jane');
console.log(john.name); // "John" (instance)
console.log(jane.name); // "Jane" (instance)
console.log(john.species); // "Human" (prototype)
console.log(jane.species); // "Human" (prototype)
// Modify prototype property
Person.prototype.species = 'Homo Sapiens';
console.log(john.species); // "Homo Sapiens" (affects all instances)
// Override in instance
john.species = 'Modified';
console.log(john.species); // "Modified" (own property)
console.log(jane.species); // "Homo Sapiens" (still uses prototype)
7. Built-in Prototypes
7.1 Object.prototype
const obj = {};
// Methods from Object.prototype
console.log(obj.toString());
console.log(obj.hasOwnProperty('name'));
console.log(obj.valueOf());
// Chain: obj → Object.prototype → null
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
7.2 Array.prototype
const arr = [1, 2, 3];
// Chain: arr → Array.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
// Array methods from Array.prototype
arr.push(4);
arr.forEach(n => console.log(n));
7.3 Function.prototype
function myFunc() {}
// Chain: myFunc → Function.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(myFunc) === Function.prototype); // true
// Function methods
console.log(myFunc.call);
console.log(myFunc.apply);
console.log(myFunc.bind);
7.4 Extending Built-in Prototypes
// Adding custom methods (use with caution!)
Array.prototype.last = function() {
return this[this.length - 1];
};
const arr = [1, 2, 3];
console.log(arr.last()); // 3
Extending built-in prototypes can cause conflicts and unexpected behavior. Use utility functions or subclasses instead.
8. Practical Patterns
8.1 Mixin Pattern
const canEat = {
eat() {
console.log(`${this.name} is eating`);
}
};
const canWalk = {
walk() {
console.log(`${this.name} is walking`);
}
};
const canSwim = {
swim() {
console.log(`${this.name} is swimming`);
}
};
// Mix behaviors into prototype
function mixin(target, ...sources) {
Object.assign(target, ...sources);
}
function Animal(name) {
this.name = name;
}
mixin(Animal.prototype, canEat, canWalk);
function Fish(name) {
Animal.call(this, name);
}
Fish.prototype = Object.create(Animal.prototype);
mixin(Fish.prototype, canSwim);
const fish = new Fish('Nemo');
fish.eat(); // "Nemo is eating"
fish.swim(); // "Nemo is swimming"
8.2 Factory Pattern with Prototypes
function createAnimal(name, type) {
const animal = Object.create(animalMethods);
animal.name = name;
animal.type = type;
return animal;
}
const animalMethods = {
eat() {
console.log(`${this.name} is eating`);
},
sleep() {
console.log(`${this.name} is sleeping`);
}
};
const dog = createAnimal('Rex', 'dog');
const cat = createAnimal('Whiskers', 'cat');
dog.eat(); // "Rex is eating"
cat.eat(); // "Whiskers is eating"
9. Performance Considerations
9.1 Property Lookup
// Faster: Own property
const obj = { name: 'John' };
console.log(obj.name); // Fast (own property)
// Slower: Deep prototype chain
const deep = Object.create(
Object.create(
Object.create({ name: 'John' })
)
);
console.log(deep.name); // Slower (3 lookups)
9.2 Caching Lookups
function Person(name) {
this.name = name;
// Cache prototype method reference
this._greet = Person.prototype.greet;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
10. Modern Alternatives
10.1 Object Composition
// Instead of inheritance, compose objects
const canEat = (state) => ({
eat: () => console.log(`${state.name} is eating`)
});
const canWalk = (state) => ({
walk: () => console.log(`${state.name} is walking`)
});
function createDog(name) {
const state = { name };
return {
...canEat(state),
...canWalk(state),
bark: () => console.log(`${name} barks`)
};
}
const dog = createDog('Rex');
dog.eat();
dog.walk();
dog.bark();
Modern JavaScript favors composition over classical inheritance for flexibility and maintainability.
Summary
In this module, you learned:
- ✅ What prototypes are and how they work
- ✅ Prototype chain and property lookup
- ✅ Constructor functions and prototypes
- ✅ Prototypal inheritance patterns
- ✅ ES6 classes vs prototypes
- ✅ Built-in prototypes
- ✅ Mixin and factory patterns
- ✅ Performance considerations
- ✅ Modern alternatives like composition
In Module 22, you'll learn about Functional Programming concepts in JavaScript.
Practice Exercises
- Implement inheritance using prototypes
- Create a mixin system for behaviors
- Build a prototype-based class hierarchy
- Compare prototype vs class performance
- Extend built-in prototypes safely
- Implement object composition patterns
- Create a factory function system
- Debug prototype chain issues