Prototypes: What Classes Are Made Of
Underneath every class, each object is linked to a prototype object that holds the shared methods, and JavaScript finds a property by walking that chain of links until it finds it or runs out.
Freelancer · JavaScript · OOP · Updated
After this lesson you can
- Explain how JavaScript finds a property by walking the prototype chain
- Tell an object's own properties from inherited ones with Object.hasOwn and Object.getPrototypeOf
Key takeaways
- An object's own properties, such as name, sit on the object itself. Its methods sit on its prototype, one shared copy for every instance.
- To find a property, JavaScript looks at the object, then its prototype, then that prototype's prototype, and stops at the first match. That path is the prototype chain.
- A fighter's chain runs Fighter.prototype, then Ship.prototype, then Object.prototype, then null, which ends it.
- Object.getPrototypeOf(x) shows x's prototype, and Object.hasOwn(x, "name") tells an own property from an inherited one.
- A class is a neater way to write what older code builds by hand with a function and its prototype.
Sooner or later you'll read code written before JavaScript had classes, with lines like Ship.prototype.describe = function () { ... }. You'll see [[Prototype]] when you open an object in the browser's DevTools, and you'll meet a bug where an object seems to have a property it was never given. All of them come from one idea: prototypes, what a class actually builds underneath. This lesson is the advanced end of the chapter. Your classes work without it, but knowing it means nothing about objects is magic anymore.
Where do the methods live?
Every Ship you build has a describe method. But is a copy of describe stored in every ship? Let's ask JavaScript. Object.hasOwn(object, "name") answers true when the property sits on the object itself:
class Ship {
constructor(name) {
this.name = name;
}
describe() {
return `${this.name} is ready`;
}
}
const comet = new Ship("Comet");
console.log(Object.hasOwn(comet, "name"));
console.log(Object.hasOwn(comet, "describe"));
console.log(Object.getPrototypeOf(comet) === Ship.prototype);
console.log(Object.hasOwn(Ship.prototype, "describe"));
true
false
true
true
name is Comet's own property: the constructor put it on Comet. describe isn't. It lives on a separate object, Ship.prototype, and every ship is linked to that object. Object.getPrototypeOf(comet) shows the object Comet is linked to: its prototype. A property an object reaches through that link, such as Comet's describe, is an inherited property.
Ask here, then ask up the chain
Think of a help desk in a big store. You ask the person at the counter; if they don't know, they ask their manager, and the manager asks the head office. Someone up the line usually knows, and you never see the asking.
That's how JavaScript reads comet.describe. It looks at Comet itself first. Not there? It looks at Comet's prototype, Ship.prototype, and finds it. With a Fighter, the line is longer:
class Ship {
describe() {
return "a ship";
}
}
class Fighter extends Ship {
fire() {
return "pew!";
}
}
const comet = new Fighter();
let link = Object.getPrototypeOf(comet);
while (link !== null) {
console.log(link.constructor.name);
link = Object.getPrototypeOf(link);
}
Fighter
Ship
Object
The loop follows the links one at a time, printing the class each prototype belongs to. Comet's prototype is Fighter.prototype (which has fire), whose prototype is Ship.prototype (which has describe), whose prototype is Object.prototype, whose prototype is null: the end of the line. That path is the prototype chain, and it's what extends builds.

Things you never wrote
That last link explains a mystery. Every object, even one with no methods at all, can do this:
class Ship {}
const comet = new Ship();
console.log(comet.toString());
console.log(Object.hasOwn(comet, "toString"));
[object Object]
false
You never wrote toString. JavaScript didn't find it on Comet or on Ship.prototype, and kept going to Object.prototype, the end of almost every chain, which gives every object a handful of built-in methods. When a property isn't anywhere on the chain, the answer is undefined.
The chain is also what instanceof checks: comet instanceof Ship is true when Ship.prototype is somewhere on Comet's chain, so a fighter is an instance of Fighter and of Ship.
Try it. Make chain.js with the Fighter example, and add class Freighter extends Ship {} above const comet. Print comet instanceof Fighter, comet instanceof Ship and comet instanceof Freighter, and explain each answer with the chain.
One copy for every instance
Because methods live on the prototype, every ship shares one copy:
class Ship {
constructor(name) {
this.name = name;
}
describe() {
return `${this.name} is ready`;
}
}
const comet = new Ship("Comet");
const vega = new Ship("Vega");
console.log(comet.describe === vega.describe);
console.log(comet.name === vega.name);
console.log(typeof Ship);
true
false
function
comet.describe === vega.describe (=== is read "strictly equal") is true: it's the very same function, so a game with a thousand ships stores describe once. Each ship's own data is separate. And typeof Ship says function: underneath, a class is a special function with a prototype object attached.
An own property wins
The lookup stops at the first match. So if an object gets an own property with the same name as one on its prototype, its own property hides the shared one, for that object only:
class Ship {
constructor(name) {
this.name = name;
}
describe() {
return `${this.name} is ready`;
}
}
const comet = new Ship("Comet");
const vega = new Ship("Vega");
vega.describe = function () {
return `${this.name} is ready, and showing off`;
};
console.log(comet.describe());
console.log(vega.describe());
console.log(Object.hasOwn(vega, "describe"));
Comet is ready
Vega is ready, and showing off
true
Vega's own describe is found first, so the search never reaches Ship.prototype. Comet has no own describe and still gets the shared one. Overriding with extends works the same way, one link up: Fighter.prototype.describe is found before Ship.prototype.describe.
Before classes: the same thing by hand
Classes arrived in JavaScript in 2015. Before that, programmers built the same chain by hand, and you'll still meet this style in older code and tutorials:
function OldShip(name) {
this.name = name;
}
OldShip.prototype.describe = function () {
return `${this.name} is ready`;
};
const relic = new OldShip("Relic");
console.log(relic.describe());
console.log(Object.getPrototypeOf(relic) === OldShip.prototype);
Relic is ready
true
A function called with new plays the constructor, and methods are added to its prototype object one by one. A class writes the same thing more neatly, which is why it's sometimes called syntactic sugar: a sweeter way to write something the language could already do. When you see .prototype. in old code, read it as "a method every instance shares".
Check yourself
2 questions to try on the lesson page.
So what
Every class, every extends and every method call runs on the prototype chain. When an object has a property you didn't expect, or an override doesn't seem to take, walk the chain with Object.getPrototypeOf and Object.hasOwn, and you'll see exactly where JavaScript found it.