Prototypes
Every object in JavaScript has a hidden link to another object called its prototype. When you read a property and the object does not have it, the engine follows that link and looks on the prototype — and on its prototype, and so on, up the prototype chain. This single mechanism is how inheritance, shared methods, and classes all work under the hood.
Every object has a prototype
Even objects created with the simplest {} literal start with a prototype: the global Object.prototype. That is why {}.toString works without you defining it.
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(obj.toString); // [Function: toString]
const arr = [];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // trueSo [] inherits from Array.prototype, which itself inherits from Object.prototype — two links to walk.
__proto__ vs getPrototypeOf
Historically the link was exposed as the __proto__ property. Modern code uses the standard accessor Object.getPrototypeOf(obj). Both refer to the same internal slot.
const obj = { x: 1 };
// Equivalent reads
console.log(obj.__proto__ === Object.getPrototypeOf(obj)); // true
// Equivalent writes — but only the second is recommended
const parent = { greet() { return "hi"; } };
Object.setPrototypeOf(obj, parent);
console.log(obj.greet()); // 'hi' — inheritedHow property lookup actually works
Read obj.x. The engine:
Looks for an own property
xonobj. If found, returns it.Otherwise looks at
Object.getPrototypeOf(obj). If found there, returns it.Otherwise climbs the chain until it reaches the top —
Object.prototype.If still not found, returns
undefined. Reading a missing property never throws.
const animal = { eats: true, breathes: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.jumps); // true — own
console.log(rabbit.eats); // true — inherited
console.log(rabbit.flies); // undefinedWriting is different from reading
Assignment always creates an own property on the target object — it does not modify the prototype. That is what keeps inheritance safe.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.eats = false; // creates an own property
console.log(rabbit.eats); // false — own
console.log(animal.eats); // true — prototype untouchedWalking the chain
const arr = [];
let node = arr;
while (node) {
console.log(node.constructor?.name ?? "null");
node = Object.getPrototypeOf(node);
}Array Object null
The chain always terminates at null — the prototype of Object.prototype.
Objects without a prototype
Sometimes you want a clean dictionary with no inherited methods at all. Pass null to Object.create to get one.
const dict = Object.create(null);
dict.hello = 1;
console.log("toString" in dict); // false — no Object.prototype
console.log(Object.getPrototypeOf(dict)); // null
// dict.toString() — would throw, because no method existshasOwn — distinguishing own from inherited
in checks the entire chain. Object.hasOwn(obj, key) checks only own properties — the modern replacement for hasOwnProperty.call.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log("eats" in rabbit); // true — inherited
console.log(Object.hasOwn(rabbit, "eats")); // false — not own
console.log(Object.hasOwn(rabbit, "jumps"));// trueThe prototype of functions
Functions are objects too, and they carry a special prototype property — confusingly named, not the same as their own [[Prototype]]. The prototype property is the object used as [[Prototype]] for instances created with new (covered next in Prototype Inheritance).
function Animal() {}
Animal.prototype.eats = true;
const rabbit = new Animal();
console.log(rabbit.eats); // true
console.log(Object.getPrototypeOf(rabbit) === Animal.prototype); // true