Prototypal inheritance
Sooner or later you write an object and think: I want another one just like this, but with a few extras. You have a user with properties and methods, and you’d like an admin and a guest that behave almost the same. Copying every method into each new object works, but now you maintain three copies of the same logic. Change one, forget the others, and the bugs start.
JavaScript solves this with prototypal inheritance: a way to build one object on top of another so the second one automatically borrows what the first one has.
[[Prototype]]
Every object in JavaScript carries a hidden internal slot the specification calls [[Prototype]]. Its value is either null or a reference to another object. That referenced object is the object’s prototype.
The rule that makes this useful is short: when you read a property that the object doesn’t have, JavaScript follows the [[Prototype]] link and looks for the property there instead. If it’s still missing, it follows that object’s link, and so on. This chained fallback is what “inheritance” means here.
[[Prototype]] is internal and hidden, but the language gives you several ways to set it. The oldest and most readable one is the special property name __proto__:
let vehicle = {
moves: true
};
let ebike = {
pedals: true
};
ebike.__proto__ = vehicle; // sets ebike.[[Prototype]] = vehicle
Now vehicle is the prototype of ebike. Whenever ebike is missing a property, JavaScript checks vehicle:
let vehicle = {
moves: true
};
let ebike = {
pedals: true
};
ebike.__proto__ = vehicle; // (*)
// we can find both properties in ebike now:
alert( ebike.moves ); // true (**)
alert( ebike.pedals ); // true
Line (*) wires up the link. When alert reads ebike.moves on line (**), that property isn’t on ebike itself, so the engine follows the [[Prototype]] reference and finds moves on vehicle. Reading ebike.pedals finds it directly, no fallback needed.
You’ll hear both phrasings: “vehicle is the prototype of ebike”, and “ebike inherits from vehicle”. Any properties that come from the prototype rather than the object itself are called inherited.
Methods inherit the same way, because a method is just a property whose value is a function:
let vehicle = {
moves: true,
drive() {
alert("Vehicle drives");
}
};
let ebike = {
pedals: true,
__proto__: vehicle
};
// drive is taken from the prototype
ebike.drive(); // Vehicle drives
ebike has no drive of its own, so the engine finds vehicle.drive up the chain and calls it. Notice you can set __proto__ right inside the object literal, which reads more cleanly than assigning it afterward.
Chains can be longer
Nothing stops a prototype from having its own prototype. The lookup walks the whole chain, one link at a time, until it finds the property or runs out of links:
let vehicle = {
moves: true,
drive() {
alert("Vehicle drives");
}
};
let ebike = {
pedals: true,
__proto__: vehicle
};
let cargoEbike = {
crateLiters: 60,
__proto__: ebike
};
// drive is taken from the prototype chain
cargoEbike.drive(); // Vehicle drives
alert(cargoEbike.pedals); // true (from ebike)
Reading cargoEbike.drive misses on cargoEbike, misses on ebike, and finally lands on vehicle. Reading cargoEbike.pedals stops one link earlier, on ebike.
Watch the lookup happen. Each button reads one property off cargoEbike; the demo climbs the chain one link at a time and shows exactly where the value was found — or reports a miss all the way to the end.
Two hard limits keep this sane:
- No cycles. You can’t link objects into a loop. Assigning
__proto__so that a chain would point back to itself throws an error. - Objects or
nullonly.[[Prototype]]can hold a reference to an object ornull. Assign a string, number, or boolean to__proto__and the engine ignores it silently.
There’s also a structural fact worth stating plainly: an object has exactly one [[Prototype]]. There is no “multiple inheritance” here — a chain is a single line, never a fork.
Writing doesn’t use the prototype
The fallback rule applies to reading only. When you write to a property, or delete one, the operation acts on the object in front of you and never touches the prototype.
Assign ebike its own drive, and from then on the object has a drive of its own:
let vehicle = {
moves: true,
drive() {
/* this method won't be used by ebike */
}
};
let ebike = {
__proto__: vehicle
};
ebike.drive = function() {
alert("Ebike zips off silently!");
};
ebike.drive(); // Ebike zips off silently!
The write created drive directly on ebike. The next call finds it immediately and never climbs to vehicle. The prototype’s drive still exists, untouched; it’s just shadowed.
There’s one exception, and it’s an important one. Accessor properties (getters and setters) don’t store a value — writing to one calls its setter function. Since a function runs, the write behaves like a method call rather than a plain assignment. So if the prototype defines a setter, writing through an inheriting object does trigger that setter:
let user = {
name: "Maya",
surname: "Vance",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // Maya Vance (*)
// setter triggers!
admin.fullName = "Nadia Kessler"; // (**)
alert(admin.fullName); // Nadia Kessler, state of admin modified
alert(user.fullName); // Maya Vance, state of user protected
On line (*), reading admin.fullName finds the getter up on user and calls it. On line (**), writing admin.fullName finds the setter on user and calls that. The interesting part is where the setter’s this.name and this.surname land — which is exactly the next question.
The value of “this”
Inside set fullName(value), what is this? The setter lives on user. Does it write to user, or to admin?
The answer cuts through a lot of confusion: prototypes have no effect on this. Whether a method is found on the object or somewhere up its prototype chain, this is always the object that appeared before the dot at the call site.
So admin.fullName = ... runs the setter with this === admin. The new name and surname get written onto admin, which is why admin changes and user stays as it was.
This behavior is what makes prototypes powerful. You can put a pile of methods on one object and let many objects inherit from it. Each inheriting object runs the shared methods but reads and writes its own state, because this is decided at call time.
Here vehicle acts as a shared “method storage”, and ebike uses it:
// vehicle has methods
let vehicle = {
drive() {
if (!this.isParked) {
alert(`I drive`);
}
},
park() {
this.isParked = true;
}
};
let ebike = {
name: "City Ebike",
__proto__: vehicle
};
// modifies ebike.isParked
ebike.park();
alert(ebike.isParked); // true
alert(vehicle.isParked); // undefined (no such property in the prototype)
ebike.park() runs vehicle’s park with this === ebike, so isParked is written onto ebike. The vehicle object never gains that property.
Add scooter, van, or any number of others inheriting from vehicle, and they all share the same method objects. But each call resolves this to its own caller, so each keeps its own state. The takeaway: methods are shared, state is not.
Try it below. Both ebike and scooter inherit the exact same park/unpark methods from one vehicle object — yet each tracks its own isParked, and vehicle itself never gains the property.
for..in loop
for..in is the one common iteration construct that walks inherited properties too, not just an object’s own keys:
let vehicle = {
moves: true
};
let ebike = {
pedals: true,
__proto__: vehicle
};
// Object.keys only returns own keys
alert(Object.keys(ebike)); // pedals
// for..in loops over both own and inherited keys
for(let prop in ebike) alert(prop); // pedals, then moves
Object.keys(ebike) reports only pedals, the object’s own key. The for..in loop reports pedals and moves, reaching into vehicle.
When you want own properties only, filter with obj.hasOwnProperty(key). It returns true only when the object holds key directly, not through the chain:
let vehicle = {
moves: true
};
let ebike = {
pedals: true,
__proto__: vehicle
};
for(let prop in ebike) {
let isOwn = ebike.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: pedals
} else {
alert(`Inherited: ${prop}`); // Inherited: moves
}
}
The full chain here is longer than it looks. ebike inherits from vehicle; vehicle is a plain {...} literal, so it inherits from Object.prototype; and Object.prototype’s own prototype is null, which caps the chain.
That chain explains a puzzle. Where did ebike.hasOwnProperty come from? You never defined it. It’s inherited from Object.prototype.hasOwnProperty — every plain object gets it for free.
But then, why doesn’t hasOwnProperty show up in the for..in loop, if for..in visits inherited properties? Because it’s non-enumerable. Every built-in property of Object.prototype carries the enumerable: false flag, and for..in only visits enumerable ones. That single flag is why the loop lists moves and pedals but stays quiet about the dozens of methods hanging off Object.prototype.
See the split for yourself. Object.keys reports only what ebike owns; for..in climbs into vehicle and lists the inherited key too — while the non-enumerable Object.prototype methods stay hidden from both.
Summary
- Every object has a hidden
[[Prototype]]slot, holding either another object ornull. obj.__proto__is a historical getter/setter for that slot;Object.getPrototypeOf/Object.setPrototypeOfare the modern equivalents.- The object the slot points to is the prototype.
- Reading a missing property (or calling a missing method) makes JavaScript search up the prototype chain.
- Writing and deleting act directly on the object and skip the prototype — unless the property is an inherited setter, which still fires.
- When an inherited method runs,
thisis the object before the dot, not the prototype. Shared methods, private state. for..initerates own and inherited enumerable properties. Every other key/value method looks at own properties only.