Every JavaScript object has a hidden link to another object called its prototype. When you access a property that does not exist directly on an object, JavaScript automatically walks up the prototype chain โ checking each prototype in turn โ until it finds the property or reaches the end of the chain (null). This is the mechanism behind every array method, every DOM method, and every string helper you use. Understanding prototypes explains why instanceof works, how method sharing saves memory, and what ES6 classes actually compile to.
The Prototype Chain
| Object | Its Prototype | Methods It Inherits |
|---|---|---|
Plain object {} |
Object.prototype |
toString, valueOf, hasOwnProperty |
Array [] |
Array.prototype |
push, map, filter, reduce |
| Function | Function.prototype |
call, apply, bind |
Array.prototype |
Object.prototype |
Arrays also inherit all Object methods |
Object.prototype |
null |
End of chain |
Prototype Utility Methods
| Method | Purpose |
|---|---|
Object.create(proto) |
Create object with specific prototype |
Object.getPrototypeOf(obj) |
Get the prototype of an object |
obj instanceof Constructor |
Check if prototype appears anywhere in chain |
Object.hasOwn(obj, key) |
Own property only โ not inherited (ES2022) |
ES5 Prototype Pattern vs ES6 Class
| Feature | ES5 Prototype | ES6 Class (same result) |
|---|---|---|
| Constructor | function Animal(name) { this.name = name; } |
class Animal { constructor(name) { this.name = name; } } |
| Add method | Animal.prototype.speak = function() {} |
speak() { } inside class body |
| Inherit | Dog.prototype = Object.create(Animal.prototype) |
class Dog extends Animal { } |
| Under the hood | The actual mechanism | Syntactic sugar over prototypes |
class Dog extends Animal compiles to the same prototype wiring as the ES5 pattern. Understanding prototypes explains every class feature: why methods are shared, how instanceof works, and what super really does.prototype are shared across all instances โ only one function object exists in memory regardless of how many instances you create. Methods defined inside the constructor (this.speak = function(){}) create a new function for every single instance. For applications creating many objects, prototype methods are significantly more memory-efficient.Array.prototype or String.prototype. Adding Array.prototype.myMethod makes it appear on every array in your entire codebase โ including inside third-party libraries โ and breaks for...in loops on arrays. Define utility functions in your own modules instead.Basic Example
// โโ How the prototype chain works โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const arr = [1, 2, 3];
// arr has no 'map' property โ JS looks up the chain:
// arr -> Array.prototype -> finds map -> calls it
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null โ end
// โโ ES5 constructor + prototype โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function Animal(name, sound) {
this.name = name; // instance property โ unique per instance
this.sound = sound;
}
// Shared method โ one copy for all instances
Animal.prototype.speak = function() {
return `${this.name} says ${this.sound}!`;
};
const cat = new Animal('Cat', 'Meow');
const dog = new Animal('Dog', 'Woof');
console.log(cat.speak()); // 'Cat says Meow!'
console.log(cat.speak === dog.speak); // true โ same function object
// โโ Inheritance via Object.create โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function Pet(name, sound, owner) {
Animal.call(this, name, sound); // call parent constructor
this.owner = owner;
}
Pet.prototype = Object.create(Animal.prototype);
Pet.prototype.constructor = Pet; // restore constructor reference
Pet.prototype.info = function() {
return `${this.speak()} (owned by ${this.owner})`;
};
const myDog = new Pet('Rex', 'Woof', 'Alice');
console.log(myDog.info()); // 'Rex says Woof! (owned by Alice)'
console.log(myDog instanceof Pet); // true
console.log(myDog instanceof Animal);// true โ chain includes Animal.prototype
// โโ Object.create for prototypal delegation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const vehicleProto = {
describe() { return `${this.make} ${this.model} (${this.year})`; },
age() { return new Date().getFullYear() - this.year; },
};
function createVehicle(make, model, year) {
return Object.assign(Object.create(vehicleProto), { make, model, year });
}
const car = createVehicle('Toyota', 'Corolla', 2020);
console.log(car.describe()); // 'Toyota Corolla (2020)'
console.log(Object.hasOwn(car, 'make')); // true โ own property
console.log(Object.hasOwn(car, 'describe')); // false โ prototype property
How It Works
Step 1 โ Property Lookup Walks the Chain
When you access arr.map, JavaScript checks if arr owns a map property. It does not โ so JavaScript checks arr‘s prototype (Array.prototype), finds map there, and returns it. This walk happens automatically and transparently for every property access.
Step 2 โ new Wires Up the Prototype
new Animal('Cat', 'Meow') creates a fresh empty object, sets its prototype to Animal.prototype, runs the constructor with this pointing to the new object, and returns it. The speak method is not copied โ it lives on the shared prototype.
Step 3 โ Object.create Sets an Explicit Prototype
Object.create(Animal.prototype) creates a new empty object whose prototype is Animal.prototype. Assigning this to Pet.prototype means every Pet instance can walk up the chain to find Animal’s methods. This is exactly what extends does automatically in ES6.
Step 4 โ Restoring constructor After Prototype Assignment
After Pet.prototype = Object.create(Animal.prototype), the constructor property of Pet.prototype points to Animal (because we replaced the original Pet.prototype). The line Pet.prototype.constructor = Pet restores the correct reference.
Step 5 โ instanceof Checks the Entire Chain
myDog instanceof Animal is true because Animal.prototype appears somewhere in myDog‘s chain โ even though the immediate prototype is Pet.prototype. instanceof traverses the entire chain looking for the constructor’s prototype.
Real-World Example: Linked List
// linked-list.js
function Node(value) {
this.value = value;
this.next = null;
}
function LinkedList() {
this.head = null;
this.length = 0;
}
LinkedList.prototype.push = function(value) {
const node = new Node(value);
if (!this.head) { this.head = node; }
else {
let cur = this.head;
while (cur.next) cur = cur.next;
cur.next = node;
}
this.length++;
return this;
};
LinkedList.prototype.toArray = function() {
const result = [];
let cur = this.head;
while (cur) { result.push(cur.value); cur = cur.next; }
return result;
};
LinkedList.prototype.find = function(predicate) {
let cur = this.head;
while (cur) {
if (predicate(cur.value)) return cur.value;
cur = cur.next;
}
return undefined;
};
const list = new LinkedList();
list.push(10).push(20).push(30).push(40);
console.log(list.toArray()); // [10, 20, 30, 40]
console.log(list.find(v => v > 25)); // 30
console.log(list.length); // 4
Common Mistakes
Mistake 1 โ Forgetting to restore constructor
โ Wrong โ Pet.prototype.constructor now points to Animal:
Pet.prototype = Object.create(Animal.prototype);
// Missing: Pet.prototype.constructor = Pet;
โ Correct โ always restore after wiring:
Pet.prototype = Object.create(Animal.prototype);
Pet.prototype.constructor = Pet;
Mistake 2 โ Shared mutable data on prototype
โ Wrong โ all instances share the same array:
Cart.prototype.items = []; // every Cart instance pushes to the SAME array!
โ Correct โ initialise per-instance data in constructor:
function Cart() { this.items = []; }
Mistake 3 โ Extending built-in prototypes
โ Wrong โ pollutes the global Array for all code:
Array.prototype.sum = function() { return this.reduce((a,b) => a+b, 0); };
โ Correct โ define utilities in your own module:
export const sum = arr => arr.reduce((a, b) => a + b, 0);
Quick Reference
| Task | Code |
|---|---|
| Get prototype | Object.getPrototypeOf(obj) |
| Create with prototype | Object.create(proto) |
| Check chain | obj instanceof Constructor |
| Own property check | Object.hasOwn(obj, 'key') |
| Add shared method | MyFn.prototype.method = function() {} |
| Set up inheritance | Child.prototype = Object.create(Parent.prototype) |
| Call parent constructor | Parent.call(this, args) |