The Prototype Chain
Prerequisites: you should already be comfortable with objects, property access, functions, and this binding from earlier chapters. We assume you know that typeof [] === 'object' and that functions are callable objects.
JavaScript uses prototypal inheritance: objects delegate to other objects. Every object has an internal link, [[Prototype]], to another object (or null). When you read a property, the engine walks this chain until it finds the property or reaches null. Writes and own-property creation do not walk the chain — assigning creates or updates a property on the receiver only.
class Greeter {
greet(name) { return `Hello, ${name}!`; }
}
const g = new Greeter();
console.log(g.greet('Ada')); // Hello, Ada!
console.log(Object.getPrototypeOf(g) === Greeter.prototype); // trueThe class syntax is sugar over the same mechanism: g has no own greet; the lookup finds it on Greeter.prototype.
const base = { describe() { return `I have ${Object.keys(this).length} keys`; } };
const derived = Object.create(base);
derived.extra = true;
console.log(derived.describe()); // methods on `base` run with `this` = derivedInternal details and gotchas
__proto__vsprototype.__proto__(now standardized viaObject.prototype) is the accessor for an instance's[[Prototype]].prototypeis a property of functions: the object thatnew F()will delegate to. Confusing these is the classic error.superis statically bound.super.foouses the home object's[[Prototype]], captured at method definition time — unlikethis, it cannot be changed bycallorbind.- Shadowing and mutation propagate. Overwriting
derived.describeshadowsbase.describe. Conversely, mutating a shared prototype object affects every delegating instance instantly — useful, and a footgun with built-ins. - Extending built-ins is brittle.
class MyArray extends Arrayhistorically broke across realms and with methods like.mapreturning plain arrays; composition (wrapping) is often safer. Object.create(null)makes a prototype-less dictionary: no inherited keys to collide with user data, but also nohasOwnProperty— useObject.hasOwn(obj, key)instead.- Setting prototypes after creation via
Object.setPrototypeOfdeoptimizes: the engine invalidates inline caches for every access through that object. PreferObject.create(proto).
Prototype chain vs. classes
| Aspect | Prototype chain | Classical classes |
|---|---|---|
| Relationship | Delegation between objects at runtime | Template stamped at instantiation |
| Property lookup | Live chain walk | Compiled to fixed member offsets |
| Reuse | Any object can be a prototype | Instances share one class's static structure |
| Refactoring | Mutating a prototype affects all instances | Requires re-instantiation or reload |
Decision criteria
Use plain prototype delegation (Object.create) for small object hierarchies and shared behavior without ceremony. Use class when you want instanceof checks, constructor semantics, and super calls — and when interop with the platform matters. Avoid deep chains (more than 3–4 levels): lookup cost grows and failure modes become hard to trace.
Exercise
Why does Object.hasOwn exist when obj.hasOwnProperty predates it?
Show answer
An object created with Object.create(null) (or one with a shadowing own property named hasOwnProperty) has no access to Object.prototype's hasOwnProperty. Object.hasOwn(obj, key) works for any object regardless of its prototype.
Spotted a mistake, or code that does not work?