Prototypes
Classes in JavaScript are a convenient syntax on top of an older mechanism — prototypes. It is useful to know: it explains where an array gets its map() method and an object its toString(), even though you never declared them.
An object's prototype
Every object has a hidden link to another object — its prototype. If a property is not found in the object itself, JavaScript looks for it in the prototype. You can create an object with a chosen prototype using Object.create():
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats); // truerabbit has no eats property of its own — it was found in the prototype animal.
Own and inherited properties
Properties written directly into an object are called its own. Object.hasOwn() tells them apart from those found in the prototype:
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(Object.hasOwn(rabbit, 'jumps')); // true
console.log(Object.hasOwn(rabbit, 'eats')); // falseIf you write a property with the same name as in the prototype, the own value hides the inherited one, and the prototype itself does not change.
The prototype chain
A prototype can have its own prototype too — this forms a chain. The property lookup goes up the chain until the property is found or the chain ends. At the end of almost every chain is Object.prototype — that is where all objects get toString() from:
const user = { name: 'Anna' };
console.log(Object.getPrototypeOf(user) === Object.prototype); // true
console.log(typeof user.toString); // functionObject.getPrototypeOf() returns an object's prototype.
Class methods live in the prototype
When you declare a method in a class, it does not go into every object but into one shared object, ClassName.prototype. All instances get it through the prototype:
class User {
greet() {
return 'Hello!';
}
}
const anna = new User();
const oleg = new User();
console.log(anna.greet === oleg.greet); // true
console.log(Object.getPrototypeOf(anna) === User.prototype); // trueThe method is stored in one place, no matter how many objects are created. extends simply links the prototypes of two classes into a chain.
Built-in prototypes
The methods of arrays, strings and numbers live in Array.prototype, String.prototype and Number.prototype. That is why every array has map(), even though you never declared it:
const numbers = [1, 2, 3];
console.log(Object.getPrototypeOf(numbers) === Array.prototype); // true
console.log(Object.hasOwn(numbers, 'map')); // falseCommon mistake
Built-in prototypes can be changed, but you should not do it. A method added to Array.prototype appears on every array in the program and may clash with a library method or a future version of the language with the same name:
// do not do this
Array.prototype.last = function () {
return this[this.length - 1];
};Write an ordinary function instead: const last = (array) => array.at(-1);.