Every JavaScript object has a hidden link to another object called its prototype. When you read a property that is not on the object, the runtime walks the prototype chain. The class keyword is built on this system. Understanding prototypes makes the language far less mysterious and lets you build patterns that classes alone cannot express.
The prototype chain
Every object has a __proto__ property (or its modern equivalent, Object.getPrototypeOf) that points to its prototype. When a property is not on the object, the runtime looks at the prototype, then the prototype's prototype, and so on until it hits null.
const animal = {
speak() { return "..."; },
};
const dog = Object.create(animal);
dog.breed = "Labrador";
console.log(dog.breed); // Labrador (own property)
console.log(dog.speak()); // ... (inherited from animal)
console.log(Object.getPrototypeOf(dog) === animal); // true
The original constructor pattern
Before class, you built objects with a constructor function and attached methods to the prototype. new creates a new object, links it to the constructor's prototype, and runs the function. This is exactly what class does under the hood.
function Dog(name, breed) {
this.name = name;
this.breed = breed;
}
Dog.prototype.speak = function () {
return "Woof";
};
Dog.prototype.showInfo = function () {
return `${this.name} (${this.breed})`;
};
const rex = new Dog("Rex", "Labrador");
console.log(rex.speak()); // Woof
console.log(rex.showInfo()); // Rex (Labrador)
class is sugar over prototypes
A class body creates a function with the same name and attaches its methods to the function's prototype. The class extends clause sets up the prototype chain. The two forms are equivalent at runtime.
class Dog {
constructor(name) { this.name = name; }
speak() { return "Woof"; }
}
// Roughly equivalent to:
function Dog2(name) { this.name = name; }
Dog2.prototype.speak = function () { return "Woof"; };
const rex = new Dog("Rex");
const rex2 = new Dog2("Rex");
console.log(typeof Dog, typeof Dog2); // function function
console.log(rex.speak(), rex2.speak()); // Woof Woof
Prototypal inheritance with Object.create
Object.create(proto) creates a new object with the given prototype. This is the cleanest form of prototypal inheritance and is the basis for many composition patterns. Pair it with Object.assign to copy properties onto the new object.
const canSpeak = {
speak() { return `${this.name} says ${this.voice}`; },
};
const canFetch = {
fetch(item) { return `${this.name} fetches ${item}`; },
};
function createAnimal(name, voice) {
const animal = Object.create(canSpeak);
animal.name = name;
animal.voice = voice;
return animal;
}
function createDog(name) {
const dog = createAnimal(name, "Woof");
return Object.assign(dog, canFetch);
}
const rex = createDog("Rex");
console.log(rex.speak()); // Rex says Woof
console.log(rex.fetch("ball")); // Rex fetches ball
Mixins for cross-cutting behaviour
A mixin is a plain object with methods. Spread or Object.assign it into a class or another object to add its behaviour. Mixins are how you get multiple-inheritance-style composition in JavaScript without class hierarchies.
const Serializable = {
toJSON() { return JSON.stringify(this); },
};
const Comparable = {
equals(other) {
return JSON.stringify(this) === JSON.stringify(other);
},
};
class Point {
constructor(x, y) { this.x = x; this.y = y; }
}
Object.assign(Point.prototype, Serializable, Comparable);
const p1 = new Point(1, 2);
const p2 = new Point(1, 2);
console.log(p1.toJSON()); // {"x":1,"y":2}
console.log(p1.equals(p2)); // true
Summary
Prototypes are how JavaScript does inheritance. Every object has a chain of prototypes, and property reads walk that chain. The class keyword is sugar over this system. Object.create and mixins give you composition patterns that are often cleaner than deep class hierarchies. The next lesson covers async JavaScript, the event loop, and the Promise and async/await syntax.
Inspecting the prototype chain
Three runtime tools let you look at the chain directly. Object.getPrototypeOf(obj) returns the immediate prototype. Object.getPrototypeOf(Object.getPrototypeOf(obj)) walks one step further. The hasOwn method on Object returns true only for properties that live on the object itself, not on its prototypes. Reflect.getPrototypeOf and Reflect.setPrototypeOf mirror the Object helpers but in the Reflect namespace. Use these when you need to inspect or modify the chain at runtime, which is rare but useful for polyfills and frameworks.
const rex = new Dog("Rex");
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Object.prototype); // true
console.log(Object.hasOwn(rex, "name")); // true
console.log(Object.hasOwn(rex, "speak")); // false (inherited)
Common prototype patterns
The most useful patterns with prototypes are mixin composition, factory functions with Object.create, and method borrowing. Method borrowing lets one object call another object's method with its own this. Array.prototype.slice.call(argumentsLike) is the classic example; today you would use Array.from(argumentsLike) instead. The hasOwnProperty trap of treating all enumerable properties as data still catches beginners. Use Object.hasOwn for the safe check, or for...in combined with Object.hasOwn to enumerate own keys.
Comparison with Python
Python's class system is more class-centric than JavaScript's. Python uses class-based inheritance with a metaclass system for advanced customization; JavaScript's class is sugar over prototypes and there is no metaclass hook. Python supports multiple inheritance with a C3 linearization algorithm; JavaScript supports single inheritance only, and you compose multiple sources through mixins or Object.assign. Python's MRO resolves which parent method wins when several parents define the same name; JavaScript's prototype chain has the same idea but is simpler because each object has exactly one prototype. The biggest practical difference for most code: Python's class methods receive self explicitly; JavaScript's methods receive this via the call site.