codejump
Academy

Prototypes and the prototype chain

How property lookup really works, what class is syntax for, and the difference between prototype and __proto__ that trips everyone up.

Updated

Lookup, not copying

Every object has a hidden link to another object — its prototype. When you read a property, the engine looks on the object itself; if it is not there, it follows the link, and keeps following until it finds the property or reaches null.

const animal = { speak() { return 'some sound'; } };
const dog = Object.create(animal);
dog.name = 'Rex';

dog.name;      // own property
dog.speak();   // not own — found one link up
dog.toString();// found two links up, on Object.prototype

Nothing was copied into dog. That is the whole difference from class-based languages: inheritance here is a live link, so adding a method to animal right now makes it available on dog immediately.

The chain for a plain array is arr → Array.prototype → Object.prototype → null. Every method you call on it lives on one of those.

prototype and __proto__ are not the same thing

This is the single most common confusion, and it is worth stating flatly:

  • `obj.__proto__` (properly Object.getPrototypeOf(obj)) is the link *this* object follows on lookup. Every object has one.
  • `Fn.prototype` is a property on a *function*, holding the object that will become __proto__ of instances the function creates with new. Only functions have it, and it is not the function's own prototype.
function User() {}
const u = new User();

Object.getPrototypeOf(u) === User.prototype;   // true
User.prototype.constructor === User;           // true

What new does, in three steps: create an empty object, set its prototype to User.prototype, run the function with this bound to it, and return it unless the function returned an object of its own.

class is this, with better syntax

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}

class Dog extends Animal {
  speak() { return `${this.name} barks`; }
}

speak is not on each dog. It is on Dog.prototype, one object shared by every instance — which is why methods cost nothing per instance and why a thousand dogs do not mean a thousand functions.

extends sets Dog.prototype.__proto__ = Animal.prototype, so lookup walks from the instance to Dog.prototype to Animal.prototype. super.speak() is a lookup that deliberately starts one link further along.

Two things classes add beyond syntax: the body runs in strict mode, and calling a class without new throws instead of silently doing something strange.

Shadowing, and the assignment asymmetry

Reading walks the chain. Writing does not.

dog.speak = () => 'woof';    // creates an OWN property on dog
delete dog.speak;            // now the inherited one is visible again

The prototype is untouched. This asymmetry is why mutating a shared prototype from instance code is nearly impossible by accident — and why Object.prototype pollution needs a deliberate path such as an unguarded __proto__ key in a deep-merge.

Telling own from inherited

Object.hasOwn(dog, 'name');     // true
Object.hasOwn(dog, 'speak');    // false — inherited
'speak' in dog;                 // true — 'in' walks the chain

for...in walks the chain too, which is why it is the wrong loop for data. Object.keys, Object.entries and JSON.stringify all look at own enumerable properties only.

When to reach for it directly

Rarely. Use class. Manipulating prototypes by hand is worth knowing because it explains what class compiles to, because Object.create(null) gives you a dictionary with no inherited keys to collide with, and because you will eventually read a library that does it.

Object.setPrototypeOf on an existing object is a genuine performance cliff — engines de-optimise objects whose shape changes that way. Build the object with the right prototype instead of moving it later.

Now practice it

Reading this page is the cheap half. These are the exercises that make you use it.