Prototypes and Inheritance
Date: 2026-09-28
Every object points at another object, and a property lookup that misses walks along those pointers. That chain is inheritance in JavaScript —
classis syntax for building it, with a handful of genuine additions on top.
A prototype is the object another object delegates to: every object has an internal [[Prototype]] link (to an object or null), and reading a property that the object doesn’t own continues the lookup along that chain.
The chain
class Product { constructor(sku) { this.sku = sku; } label() { return this.sku; } }
class Bundle extends Product { label() { return 'Bundle ' + super.label(); } }
const b = new Bundle('B-1');b own: sku = 'B-1'
│ [[Prototype]]
▼
Bundle.prototype own: label (Bundle's), constructor
│
▼
Product.prototype own: label (Product's), constructor
│
▼
Object.prototype own: toString, hasOwnProperty, …
│
▼
null ← lookup ends: undefined
Reads walk the chain; writes don’t. b.label() finds label on Bundle.prototype. b.label = … creates an own property on b that shadows it — the prototype is untouched. (Exceptions: a setter or a non-writable property further up the chain intercepts the write.)
Methods live once, on the prototype. A thousand Bundle instances share one label function. That’s the memory argument against per-instance arrow fields — this and Binding.
What class is sugar over
Class and constructor-function forms — equivalent chains:
// class syntax
class Product {
constructor(sku) { this.sku = sku; }
label() { return this.sku; }
}
// what it builds — the pre-2015 form
function Product(sku) { this.sku = sku; }
Product.prototype.label = function () { return this.sku; };Both produce the same shape: a function whose .prototype object holds the methods, and instances linked to that object. But class is not pure sugar. What it adds that the old form doesn’t:
- Must be called with
new— calling it as a function throws - Strict mode throughout the body
- Methods are non-enumerable — they don’t show up in
for...in - Not initialised until its declaration runs — a TDZ like
let— Scope and Hoisting super— methods remember the object they were defined on, sosuper.label()works; there’s no clean pre-class equivalent- Derived constructors must call
super()before touchingthis, because the parent creates the object — which is why subclassing built-ins likeArrayandErrorfinally works properly #privatefields — genuinely new. Stored per instance, invisible to the chain,Object.keys, proxies and reflection — not a naming convention
Useful tools
Object.create(proto)— make an object with a chosen prototype, no constructor involvedObject.create(null)— an object with no prototype: a safe dictionary, with no inheritedtoStringor__proto__to collide with keys.Mapis usually better stillObject.hasOwn(obj, key)— is this own, not inheritedObject.getPrototypeOf(obj)— read the link. Changing it after creation (Object.setPrototypeOf) is legal and deoptimises the object in engines, so don’t
Where it goes wrong
- Prototype pollution. A merge or clone function that walks untrusted JSON writes
{"__proto__": {"isAdmin": true}}intoObject.prototype— and now every object in the process hasisAdmin. A real class of server-side vulnerability — Common Vulnerabilities. Guard: skip__proto__,constructorandprototypekeys, or merge intoObject.create(null) - Extending built-in prototypes. Adding
Array.prototype.flattenin a library collides with the language later adding its own. This happened: MooTools definedflatten, so the standard method shipped asflatinstead, because the proper name would have broken live sites (“SmooshGate”, 2018). Polyfill only standard methods, and only if missing — Shims and Polyfills instanceofacross realms. An array from an iframe failsinstanceof Array, because it links to that window’sArray.prototype. UseArray.isArray— Iframes and Sandboxing- Deep hierarchies. Four levels of
extendscouples every subclass to every ancestor’s internals. Composition — objects holding other objects — is the usual answer, in JS as elsewhere — Coupling and Cohesion
vs PHP / C#: those have classes as blueprints, and objects are stamped from them. JavaScript has only objects linked to objects; a “class” is a function plus an object that instances point at, and it can be modified at runtime.