Tags: web-dev concept

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 — class is 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, so super.label() works; there’s no clean pre-class equivalent
  • Derived constructors must call super() before touching this, because the parent creates the object — which is why subclassing built-ins like Array and Error finally works properly
  • #private fields — 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 involved
  • Object.create(null) — an object with no prototype: a safe dictionary, with no inherited toString or __proto__ to collide with keys. Map is usually better still
  • Object.hasOwn(obj, key) — is this own, not inherited
  • Object.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}} into Object.prototype — and now every object in the process has isAdmin. A real class of server-side vulnerability — Common Vulnerabilities. Guard: skip __proto__, constructor and prototype keys, or merge into Object.create(null)
  • Extending built-in prototypes. Adding Array.prototype.flatten in a library collides with the language later adding its own. This happened: MooTools defined flatten, so the standard method shipped as flat instead, because the proper name would have broken live sites (“SmooshGate”, 2018). Polyfill only standard methods, and only if missing — Shims and Polyfills
  • instanceof across realms. An array from an iframe fails instanceof Array, because it links to that window’s Array.prototype. Use Array.isArray — Iframes and Sandboxing
  • Deep hierarchies. Four levels of extends couples 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.