The prototype chain is a core JavaScript concept that trips up many people. This post uses diagrams and simple examples to explain it clearly.
Core Concepts
Every object has an internal property [[Prototype]] (accessible via __proto__) that points to its "prototype object". When you access a property, if the object doesn't have it, JavaScript walks up the chain — this is the prototype chain.
Function, Prototype, and __proto__
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
return `Hello, I'm ${this.name}`;
};
const alice = new Person("Alice");
// Prototype chain:
// alice.__proto__ === Person.prototype ✓
// Person.prototype.__proto__ === Object.prototype ✓
// Object.prototype.__proto__ === null (chain ends) ✓
console.log(alice.sayHello()); // "Hello, I'm Alice"
// Property lookup:
// alice.sayHello → not found on alice
// → look at alice.__proto__ (Person.prototype)
// → found! return it
ASCII diagram of the relationships:
alice ─── __proto__ ──→ Person.prototype ─── __proto__ ──→ Object.prototype ─── __proto__ ──→ null
↑ ↑
constructor constructor
| |
Person (built-in)
|
prototype ──→ Person.prototype
What new Does
javascript
// new Person('Alice') actually does:
function myNew(Constructor, ...args) {
// 1. Create a new empty object
const obj = {};
// 2. Set prototype: obj.__proto__ = Constructor.prototype
Object.setPrototypeOf(obj, Constructor.prototype);
// 3. Call the constructor, passing the new object as `this`
const result = Constructor.apply(obj, args);
// 4. If the constructor returns an object, use that; otherwise return obj
return result instanceof Object ? result : obj;
}
const alice = myNew(Person, "Alice");
ES5 Inheritance (Object.create)
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound`;
};
function Dog(name, breed) {
Animal.call(this, name); // call parent constructor
this.breed = breed;
}
// The key line: set Dog's prototype to Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // fix the constructor reference
Dog.prototype.bark = function () {
return `${this.name} barks`;
};
const rex = new Dog("Rex", "Husky");
rex.speak(); // "Rex makes a sound" — from Animal
rex.bark(); // "Rex barks" — from Dog
ES6 Class: Syntactic Sugar
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // equivalent to Animal.call(this, name)
this.breed = breed;
}
bark() {
return `${this.name} barks`;
}
}
// The underlying prototype chain is identical to the ES5 version above
Property Lookup Rules
javascript
const obj = { a: 1 };
const child = Object.create(obj);
child.b = 2;
// Lookup child.a:
// 1. Look for 'a' on child → not found
// 2. Look for 'a' on child.__proto__ (= obj) → found! return 1
// hasOwnProperty: checks only the object itself, not the prototype chain
child.hasOwnProperty("a"); // false
child.hasOwnProperty("b"); // true
// for...in traverses the entire prototype chain
for (const key in child) {
console.log(key); // outputs: b, a
}
// Only own properties
for (const key in child) {
if (child.hasOwnProperty(key)) {
console.log(key); // outputs: b
}
}
Classic Interview Questions
javascript
// Question 1: What is typeof null?
typeof null; // "object" — historical JavaScript bug
// Question 2: What does instanceof check?
[] instanceof Array; // true — checks if Array.prototype is in []'s prototype chain
[] instanceof Object; // true — Array.prototype.__proto__ === Object.prototype
// Question 3: Prototype chain after Object.create(null)
const obj = Object.create(null);
obj.__proto__; // undefined — no prototype chain at all
// Used for truly clean dictionary objects, avoiding toString, hasOwnProperty etc.
Summary
- Each object has
__proto__pointing to its prototype; property lookup walks up the chain - Functions have a
prototypeproperty; afternew, the instance's__proto__is set to it classis syntactic sugar, the underlying mechanism is still the prototype chainhasOwnPropertychecks own properties only;inoperator traverses the full chain