Objects, prototypes, and prototypal inheritance

JavaScript doesn't have classical inheritance underneath its class syntax at all — every object has a live link to another object it delegates to when a property lookup fails, and that link, the prototype chain, is the actual mechanism behind every inherited method.

Beginner

3 min read

Where .toString() and .hasOwnProperty() actually come from

const user = { name: "Ada" };
user.toString(); // "[object Object]" — but toString was never defined on `user` itself

user was defined with exactly one property (name), yet calling .toString() on it works — because when a property lookup fails on the object itself, JavaScript doesn't just give up; it checks the object's prototype, an internal link to another object, and if the lookup fails there too, it checks that object's prototype, continuing until it either finds the property or runs out of prototypes. user's prototype is Object.prototype, which does define toString — this chain of prototype links is called the prototype chain, and it's the actual mechanism behind every method an object seems to have "for free."

Object.create(): the raw mechanism underneath everything else

const animal = {
  speak() { return `${this.name} makes a sound`; },
};
 
const dog = Object.create(animal); // dog's prototype is explicitly set to animal
dog.name = "Rex";
dog.speak(); // "Rex makes a sound" — speak isn't ON dog, it's found via the prototype chain

Object.create(proto) creates a brand-new object whose prototype is set directly to whatever's passed in — this is the actual, low-level mechanism of prototypal inheritance, with no class syntax involved at all. dog genuinely has no speak property of its own; every call to dog.speak() walks the prototype chain up to animal to find it, exactly the same lookup process demonstrated with toString above, just with a manually constructed chain instead of the built-in Object.prototype.

class syntax is real, working sugar over exactly this mechanism

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}
 
class Dog extends Animal {
  speak() { return `${this.name} barks`; }
}
 
const rex = new Dog("Rex");
rex.speak();        // "Rex barks" — Dog's own speak, found first
Object.getPrototypeOf(rex) === Dog.prototype; // true — rex's prototype IS Dog.prototype
Object.getPrototypeOf(Dog.prototype) === Animal.prototype; // true — Dog.prototype's prototype IS Animal.prototype

class and extends didn't add a genuinely new inheritance model to JavaScript — they're a cleaner, more familiar syntax over the exact same prototype-chain mechanism that Object.create demonstrates directly. class Dog extends Animal sets up Dog.prototype's own prototype to be Animal.prototype, and every method defined in a class body is placed onto that class's .prototype object, not directly onto each instance — which is why methods are shared across every instance of a class (one copy on the shared prototype) while instance data (this.name) genuinely differs per instance.

Why prototype methods are shared, but instance properties aren't

const rex = new Dog("Rex");
const fido = new Dog("Fido");
rex.speak === fido.speak; // true — the SAME function, found via the shared Dog.prototype
rex.name === fido.name;   // false — "Rex" !== "Fido", each instance's OWN property

Every Dog instance shares the exact same speak function object — it lives once, on Dog.prototype, and every instance finds it via the prototype chain rather than having its own copy — a genuine memory-efficiency benefit at scale, since a thousand Dog instances don't need a thousand copies of an identical function. name, by contrast, is set directly on each instance in the constructor (this.name = name), so it's a real, separate value per object, not shared through the chain at all.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. Where does a plain object like `{ name: 'Ada' }` get its `.toString()` method from, if it was never defined on the object itself?

2. What does `Object.create(animal)` actually do?

3. What does `class Dog extends Animal` actually do under the hood?