Prototypes: The Truth About Objects
Advanced · 18 min read · ▶ live playground · ✦ checkpoint
You can call player.label() even when player does not own label. That sounds impossible until you see the hidden link. A prototype is another object used as a backup for missing property lookups, and prototypes are JavaScript's real object model.
Objects can use a backup object
From the memory lesson, objects live in the heap and variables hold reference tickets to them. A prototype is another reference ticket: one heap object points at another heap object.
Picture the object as a labeled card. Its prototype is a backup card behind it. If the front card does not have the label you asked for, JavaScript checks the backup card.
Object.create(prototypeObject) creates a new object whose prototype is the object you pass in. An own property is a property stored directly on the object, not found through its prototype. Object.hasOwn(obj, key) is true only when the property lives directly on obj, not on its prototype.
const playerTools = {
label() {
return `${this.name}: ${this.score} points`;
},
addPoints(points) {
this.score = this.score + points;
return this.score;
},
};
const mina = Object.create(playerTools);
mina.name = "Mina";
mina.score = 18;
console.log(mina.label());
mina.addPoints(4);
console.log(mina.label());
console.log(Object.hasOwn(mina, "label"));
console.log(Object.hasOwn(playerTools, "label"));→ Mina: 18 points, Mina: 22 points, false, then true.
mina owns name and score. It does not own label or addPoints. Those methods live on playerTools, and mina uses them through its prototype.
Property lookup walks upward
A property lookup is the engine's search for a property name when you read object.name or call object.method(). A prototype chain is the linked path JavaScript walks from one object to its prototype, then to that prototype's prototype, until it finds the property or reaches null.
const defaultPlayer = {
mood: "focused",
rank: "rookie",
describe() {
return `${this.name} is ${this.mood} (${this.rank})`;
},
};
const nia = Object.create(defaultPlayer);
nia.name = "Nia";
nia.mood = "excited";
const omar = Object.create(defaultPlayer);
omar.name = "Omar";
console.log(nia.describe());
console.log(omar.describe());
console.log(Object.getPrototypeOf(nia) === defaultPlayer);
console.log(Object.getPrototypeOf(defaultPlayer) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));→ Nia is excited (rookie), Omar is focused (rookie), true, true, then null.
nia.mood is an own property, so it wins. omar.mood is missing on omar, so JavaScript walks to defaultPlayer and finds "focused".
That "wins" behavior is shadowing, which means an own property hides a same-named inherited property for that lookup. The backup card still exists. The front card just answers first.
Object.prototype is the ordinary base object most object chains eventually reach. Its prototype is null, which means "there is no next card."
__proto__ is not prototype
Two names cause years of confusion.
__proto__ is an old property-like hook many objects expose for their actual prototype; prefer Object.getPrototypeOf(object) when you need to read that link. A constructor function's prototype is the object new puts behind objects that constructor creates.
function PlayerCard(playerName, score) {
this.playerName = playerName;
this.score = score;
}
PlayerCard.prototype.label = function () {
return `${this.playerName}: ${this.score}`;
};
const card = new PlayerCard("Ira", 16);
console.log(card.label());
console.log(Object.getPrototypeOf(card) === PlayerCard.prototype);
console.log(card.__proto__ === PlayerCard.prototype);
console.log(Object.getPrototypeOf(PlayerCard) === PlayerCard.prototype);→ Ira: 16, true, true, then false.
A constructor function is a regular function intended to be called with new to create objects. The light version is this: new PlayerCard(...) creates card, and card gets PlayerCard.prototype as its prototype.
So card.__proto__ and PlayerCard.prototype can point at the same object, but they ask different questions:
card.__proto__: "What is this object's actual backup object?"PlayerCard.prototype: "What backup object shouldnew PlayerCard(...)use?"
You will see __proto__ in old code, console experiments, and diagrams. In code you write, reach for Object.create(...), Object.getPrototypeOf(...), and the class syntax coming next.
Try it — share one tool set
Run this, then give only ava her own report method. Watch Object.hasOwn(ava, "report") change while leo keeps using the shared method.
Inheritance before classes
Inheritance means one object can reuse behavior from another object by letting missing property lookups continue up the prototype chain. You do not need class syntax to do that.
const basicMember = {
plan: "basic",
canEnter(room) {
return room === "lobby";
},
status() {
return `${this.name}: ${this.plan}`;
},
};
const proMember = Object.create(basicMember);
proMember.name = "Mina";
proMember.plan = "pro";
proMember.canEnter = function (room) {
return room === "lobby" || room === "studio";
};
console.log(proMember.status());
console.log(proMember.canEnter("studio"));
console.log(basicMember.canEnter("studio"));
console.log(Object.getPrototypeOf(proMember) === basicMember);→ Mina: pro, true, false, then true.
proMember inherits status from basicMember, but the receiver is still proMember, so this.name and this.plan read Mina's own data. proMember also owns its own canEnter, so that method shadows the basic version.
Syntactic sugar means a nicer spelling over behavior the language already has. JavaScript classes are syntactic sugar over prototypes: they give this object model a cleaner shape, but they do not replace it.
You now know the machinery classes sit on. Next, you give that machinery its everyday spelling: class, constructor, new, and the checks people use on created objects.
Checkpoint
Answer all three to mark this lesson complete