Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Прототипы

Классы в 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); // function

Object.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);.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.