JavaScriptPrototypes

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.

JS
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);  // true

So [] 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.

JS
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' — inherited
Don't change prototypes at runtime
`Object.setPrototypeOf` works, but it is slow — engines optimise objects based on their initial shape. Pick a prototype at creation time with `Object.create` or a class, not later.
How property lookup actually works

Read obj.x. The engine:

  • Looks for an own property x on obj. 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.

JS
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);    // undefined
Writing 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.

JS
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 untouched
Walking the chain

JS
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.

JS
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 exists
hasOwn — distinguishing own from inherited

in checks the entire chain. Object.hasOwn(obj, key) checks only own properties — the modern replacement for hasOwnProperty.call.

JS
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"));// true
The 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).

JS
function Animal() {}
Animal.prototype.eats = true;

const rabbit = new Animal();
console.log(rabbit.eats);                                  // true
console.log(Object.getPrototypeOf(rabbit) === Animal.prototype);  // true
Two different ideas, similar names
`obj.__proto__` (or `Object.getPrototypeOf(obj)`) is the link the object *itself* uses for lookup. `fn.prototype` is the link that *instances* of `fn` will use. They are not the same property.
One sentence
A prototype is just another object that the engine looks at when a property is missing on this one — and that chain of "look one step up" is how every form of JavaScript inheritance works.