Прототипы
Классы в JavaScript — удобная запись поверх более старого механизма — прототипов. Знать его полезно: он объясняет, откуда у массива метод map(), а у объекта — toString(), хотя вы их не объявляли.
Прототип объекта
У каждого объекта есть скрытая ссылка на другой объект — его прототип. Если свойства нет в самом объекте, JavaScript ищет его в прототипе. Создать объект с нужным прототипом можно через Object.create():
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats); // trueУ rabbit нет своего свойства eats — оно найдено в прототипе animal.
Свои и унаследованные свойства
Свойства, записанные прямо в объект, называются собственными. Object.hasOwn() отличает их от найденных в прототипе:
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')); // falseЕсли записать в объект свойство с тем же именем, что в прототипе, собственное значение закроет унаследованное, а сам прототип не изменится.
Цепочка прототипов
У прототипа тоже может быть прототип — так получается цепочка. Поиск свойства идёт по ней вверх, пока свойство не найдётся или цепочка не кончится. В конце почти любой цепочки стоит Object.prototype — оттуда у всех объектов метод toString():
const user = { name: 'Анна' };
console.log(Object.getPrototypeOf(user) === Object.prototype); // true
console.log(typeof user.toString); // functionObject.getPrototypeOf() возвращает прототип объекта.
Методы классов живут в прототипе
Когда вы объявляете метод в классе, он попадает не в каждый объект, а в один общий объект Класс.prototype. Все экземпляры получают его через прототип:
class User {
greet() {
return 'Привет!';
}
}
const anna = new User();
const oleg = new User();
console.log(anna.greet === oleg.greet); // true
console.log(Object.getPrototypeOf(anna) === User.prototype); // trueМетод хранится в одном месте, сколько бы объектов ни было создано. extends просто связывает прототипы двух классов в цепочку.
Встроенные прототипы
У массивов, строк и чисел методы лежат в Array.prototype, String.prototype и Number.prototype. Поэтому у любого массива есть map(), хотя вы его не объявляли:
const numbers = [1, 2, 3];
console.log(Object.getPrototypeOf(numbers) === Array.prototype); // true
console.log(Object.hasOwn(numbers, 'map')); // falseЧастая ошибка
Встроенные прототипы можно изменить, но делать этого не стоит. Метод, добавленный в Array.prototype, появится у всех массивов программы и может столкнуться с одноимённым методом библиотеки или будущей версии языка:
// так делать не нужно
Array.prototype.last = function () {
return this[this.length - 1];
};Вместо этого напишите обычную функцию: const last = (array) => array.at(-1);.