JavaScript has classes, but it is still a prototype-based language. The class keyword is a cleaner way to write what the language was already doing. It does not replace the prototype system.
The same thing, two ways
// The old way
function PersonOld(name) {
this.name = name;
}
PersonOld.prototype.greet = function () {
return "Hi " + this.name;
};
// The class way
class PersonNew {
constructor(name) {
this.name = name;
}
greet() {
return "Hi " + this.name;
}
}
console.log(new PersonOld("Maya").greet()); // → Hi Maya
console.log(new PersonNew("Maya").greet()); // → Hi Maya
console.log(typeof PersonNew); // → function
console.log(typeof PersonNew.prototype.greet); // → function
console.log(Object.getPrototypeOf(new PersonNew("x")) === PersonNew.prototype); // → true
A class is a function. Its methods are stored on ClassName.prototype, exactly as in the old style.
So what does class add?
Classes are not just “make-up”. They fix several problems:
| Feature | Constructor function | Class |
|---|---|---|
Calling without new | silently wrong | throws an error |
| Methods | enumerable properties | non-enumerable (cleaner for...in) |
| Inheritance | manual and fiddly | extends and super |
| Strict mode | optional | always |
Private fields (#x) | not available | yes |
| Hoisting | the function is hoisted | not usable before the declaration |
static, getters/setters | manual | built in |
Inheritance maps to the prototype chain
class Animal { speak() { return "sound"; } }
class Dog extends Animal {}
const rex = new Dog();
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // → true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // → true
rex → Dog.prototype → Animal.prototype → Object.prototype → null
extends simply wires up this chain for you.
What this means in practice
- You can use classes without thinking about prototypes most of the time.
- When something behaves strangely (a lost
this, a method that shows up in odd places,instanceofresults), the prototype model explains why. - Libraries that predate classes, and a lot of existing code, use constructor functions. You can read both.
- You can still mix styles. A class instance is an ordinary object with a prototype, so
Object.create,Object.getPrototypeOfand friends work on it.
Methods on the prototype are shared
class Counter {
count = 0;
increment() { this.count++; }
}
const a = new Counter();
const b = new Counter();
console.log(a.increment === b.increment); // → true (one method, on the prototype)
console.log(Object.hasOwn(a, "count")); // → true (the field is per instance)
console.log(Object.hasOwn(a, "increment")); // → false
Fields (data) belong to each instance. Methods are shared on the prototype. An arrow function assigned to a field is not shared: each instance gets its own copy.