Prototypes explain how classes, methods and instanceof actually work, and why some objects behave unexpectedly.
Every object has an internal link, [[Prototype]], to another object (or null). When you read a property that the object doesn't own, the engine looks it up on the prototype, then the prototype's prototype, and so on — the prototype chain — until it finds it or reaches null and returns undefined.
Writing is different: assigning obj.x = 1 creates or updates an own property on obj (unless the chain has a setter), shadowing any inherited x.
Inspect and set the link with Object.getPrototypeOf(obj) and Object.create(proto). You'll also see obj.__proto__; it's a legacy accessor — avoid it in new code. Object.setPrototypeOf exists but changing a live object's prototype is slow.
Classes are built on this. For class Dog extends Animal:
- methods are stored once on
Dog.prototype, shared by every instance; - each instance's
[[Prototype]]isDog.prototype; Dog.prototype's[[Prototype]]isAnimal.prototype, thenObject.prototype, thennull;- static members live on the constructor, and
Dog's own prototype isAnimal.
instanceof simply checks whether Ctor.prototype appears in the object's chain. Built-ins work the same way: [].map is found on Array.prototype.
Two practical consequences:
Object.hasOwn(obj, key)distinguishes own from inherited properties;inandfor...insee inherited ones too.- Plain
{}inherits fromObject.prototype, so"constructor" in {}is true. For a pure dictionary useObject.create(null)— or aMap.
Never add methods to built-in prototypes such as Array.prototype in application code; it collides with future standard methods and other libraries.
Example
const animal = {
speak() { return `${this.name} makes a sound`; },
};
const dog = Object.create(animal); // dog -> animal -> Object.prototype
dog.name = "Rex";
console.log(dog.speak()); // Rex makes a sound (inherited)
console.log(Object.hasOwn(dog, "speak")); // false
dog.speak = function () { return "Woof"; }; // own property shadows
console.log(dog.speak(), animal.speak.call(dog)); // Woof Rex makes a sound
class Shape { area() { return 0; } }
class Square extends Shape {
constructor(s) { super(); this.s = s; }
area() { return this.s ** 2; }
}
const sq = new Square(3);
console.log(Object.getPrototypeOf(sq) === Square.prototype); // true
console.log(Object.getPrototypeOf(Square.prototype) === Shape.prototype); // true
console.log(Object.hasOwn(sq, "area"), sq instanceof Shape); // false true
const dict = Object.create(null); // no inherited keys at all
console.log("toString" in dict, "toString" in {}); // false trueRemember
- Reads walk the prototype chain; writes create own properties
- Class methods live on
Class.prototype, shared by all instances instanceofchecks whetherCtor.prototypeis in the chain
Watch out for
- Extending built-in prototypes like
Array.prototypein app code.