What it is: JavaScript’s way of sharing properties and methods between objects without copying them.
The Core Concept - Hidden [[Prototype]] Property:
Every object has a secret hidden property called [[Prototype]] that points to another object (or null).
javascript
let animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
let rabbit = {
jumps: true
};
// Connect rabbit to animal
rabbit.__proto__ = animal;What happens: rabbit now has access to everything in animal without copying it.
How Property Lookup Works - The Chain:
When you access a property, JavaScript follows this process:
- Check the object itself - Does
rabbithave the property? - If not found, check prototype - Does
rabbit.__proto__(which isanimal) have it? - Keep going up the chain - Continue until found or reach
null
console.log(rabbit.jumps); // true (found in rabbit itself)
console.log(rabbit.eats); // true (found in animal via __proto__)
rabbit.walk(); // "Animal walks" (method from animal)Visual flow:
rabbit.eats → not in rabbit → check rabbit.__proto__ (animal) → found eats: true
Longer Prototype Chains:
javascript
let animal = {
eats: true,
walk() { console.log("Walking..."); }
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// Chain: longEar → rabbit → animal → Object.prototype → null
longEar.walk(); // "Walking..." (found in animal)
longEar.jumps; // true (found in rabbit)
longEar.eats; // true (found in animal)Key Rules of Prototypes:
1. Reading goes up the chain, writing stays local:
javascript
let animal = {
eats: true,
walk() { console.log("Animal walks"); }
};
let rabbit = {
__proto__: animal
};
// Reading - goes up chain
console.log(rabbit.eats); // true (from animal)
// Writing - stays in rabbit
rabbit.eats = false;
console.log(rabbit.eats); // false (rabbit's own property now)
console.log(animal.eats); // true (animal unchanged)2. Methods use this of the calling object:
javascript
let animal = {
name: "Generic Animal",
introduce() {
console.log(`I am ${this.name}`);
}
};
let rabbit = {
name: "Bunny",
__proto__: animal
};
rabbit.introduce(); // "I am Bunny" (this = rabbit, not animal)This is crucial: The method comes from animal, but this points to rabbit!
Rule : This always points to obj before dot , so this = rabit obj so used the name from there
Prototype Chain Limitations:
1. No circular references:
javascript
let a = {};
let b = {};
a.__proto__ = b;
b.__proto__ = a; // Error! Circular __proto__ value2. __proto__ can only be object or null:
javascript
rabbit.__proto__ = "string"; // Ignored, __proto__ stays unchanged
rabbit.__proto__ = 42; // Ignored
rabbit.__proto__ = null; // OK - breaks the chain3. Only one prototype per object:
javascript
// Can't inherit from multiple objects directly
rabbit.__proto__ = animal;
rabbit.__proto__ = machine; // This overwrites the previous onePractical Example - Method Sharing:
javascript
let animal = {
eat() {
this.full = true;
console.log(`${this.name} is eating`);
},
sleep() {
this.sleeping = true;
console.log(`${this.name} is sleeping`);
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
let snake = {
name: "Python",
__proto__: animal
};
rabbit.eat(); // "White Rabbit is eating" (sets rabbit.full = true)
snake.eat(); // "Python is eating" (sets snake.full = true)
console.log(rabbit.full); // true
console.log(snake.full); // true
console.log(animal.full); // undefined (animal wasn't modified)__proto__ vs [[Prototype]]:
Important distinction:
[[Prototype]]= The actual hidden internal property__proto__= A getter/setter that accesses[[Prototype]]
javascript
// These are equivalent:
rabbit.__proto__ = animal;
Object.setPrototypeOf(rabbit, animal);
// These are equivalent:
console.log(rabbit.__proto__);
console.log(Object.getPrototypeOf(rabbit));Modern way (preferred):
javascript
// Set prototype
Object.setPrototypeOf(rabbit, animal);
// Get prototype
Object.getPrototypeOf(rabbit);
// Create object with specific prototype
let rabbit = Object.create(animal);Mental Model:
Think of prototypes as a fallback mechanism:
- “Do I have this property/method myself?”
- “No? Let me check my prototype”
- “Not there either? Let me check my prototype’s prototype”
- Continue until found or hit
null
Bottom line: Prototypal inheritance lets objects share functionality without copying. Objects can access properties/methods from their prototype chain, but modifications only affect the object itself. Use __proto__ for learning, but prefer Object.setPrototypeOf in real code
__proto__ is a getter/setter:
javascript
let animal = { eats: true };
let rabbit = {};
// SETTER - when you assign to __proto__
rabbit.__proto__ = animal; // Sets [[Prototype]] = animal
// GETTER - when you read __proto__
console.log(rabbit.__proto__); // Reads [[Prototype]], returns animal[[Prototype]] is the actual connection:
javascript
// [[Prototype]] is the hidden internal property that stores the reference
// Think of it like:
rabbit.[[Prototype]] = animal; // (This syntax doesn't exist, just for understanding)
// When you do rabbit.eats:
// 1. Check rabbit itself - not found
// 2. Check rabbit.[[Prototype]] (which is animal) - found!Visual representation:
rabbit = {
jumps: true,
[[Prototype]]: → animal = { eats: true }
}
The inheritance chain lookup:
javascript
let grandparent = { walks: true };
let parent = { eats: true, __proto__: grandparent };
let child = { jumps: true, __proto__: parent };
child.walks;
// 1. Check child - no 'walks'
// 2. Check child.[[Prototype]] (parent) - no 'walks'
// 3. Check parent.[[Prototype]] (grandparent) - found 'walks'!Modern alternatives do the same thing:
Old way (2 steps):
javascript
let rabbit = {}; // Create object
rabbit.__proto__ = animal; // Set prototypeModern way 1 (2 steps):
javascript
let rabbit = {}; // Create object
Object.setPrototypeOf(rabbit, animal); // Set prototypeModern way 2 (1 step):
javascript
let rabbit = Object.create(animal); // Create object WITH prototype already setAll three create the same result:
javascript
rabbit.[[Prototype]] === animal // true for all methodsQuick comparison:
javascript
let animal = { eats: true };
// Method 1: __proto__
let rabbit1 = { jumps: true };
rabbit1.__proto__ = animal;
// Method 2: setPrototypeOf
let rabbit2 = { jumps: true };
Object.setPrototypeOf(rabbit2, animal);
// Method 3: Object.create + adding properties
let rabbit3 = Object.create(animal);
rabbit3.jumps = true;
// All are identical:
console.log(rabbit1.eats); // true
console.log(rabbit2.eats); // true
console.log(rabbit3.eats); // trueThe setter/getter behavior you mentioned:
javascript
let animal = { eats: true };
let rabbit = {};
// WRITE (setter) - sets the [[Prototype]]
rabbit.__proto__ = animal;
// READ (getter) - reads the [[Prototype]]
let parent = rabbit.__proto__; // Returns animal
// Check what happened:
console.log(Object.getPrototypeOf(rabbit) === animal); // true
console.log(rabbit.__proto__ === animal); // trueYour mental model is perfect:
[[Prototype]]= The actual hidden “parent pointer”__proto__= The way to get/set that pointer- Inheritance chain = “If I don’t have it, check my parent, then grandparent, etc.”
- Object.create() = “Create object with parent already connected”
