Цепочка прототипов
Вернёмся на мебельную фабрику. Инструкцию «как сложить стул» не приклеивают к каждому стулу: она лежит в папке чертежа, одна на все стулья. Если работник не нашёл ответа на самом стуле, он открывает папку его чертежа. Не нашёл и там — берёт общий справочник фабрики, один на всю мебель.
Объекты в JavaScript ищут свойства точно так же. Node.js печатает экземпляр класса только с его данными: в Animal { name: 'Rex' } метода greet не видно. И всё же вызов rex.greet() работает. Метод лежит не в самом объекте:
class Animal {
constructor(name) {
this.name = name;
}
greet() {
return `I am ${this.name}`;
}
}
const rex = new Animal("Rex");
console.log(rex); // Animal { name: 'Rex' }
console.log(Object.hasOwn(rex, "name"), Object.hasOwn(rex, "greet")); // true false
console.log(Object.getPrototypeOf(rex) === Animal.prototype); // trueДавайте разберём три строки вывода:
console.log(rex)показывает толькоname— данные самого объекта.Object.hasOwnподтверждает:nameв объекте есть, аgreetнет.- Последняя строка печатает
true: прототипrex— объектAnimal.prototype. Это и есть папка чертежа.
Собственные свойства
Функция Object.hasOwn(obj, key) проверяет, есть ли у объекта собственное свойство с таким ключом. Такое свойство записано в сам объект. Свойство name записал constructor, а greet в объекте нет.
Прототип
Каждый объект хранит ссылку на свой прототип: другой объект или null. Получить его можно функцией Object.getPrototypeOf.
У каждого класса есть свойство prototype: в нём лежит объект, который new делает прототипом новых экземпляров. У экземпляров класса Animal прототип — объект Animal.prototype, и в нём лежат методы из тела класса, кроме статических. Поэтому метод один на все экземпляры.
Поиск по цепочке
Если свойства нет в самом объекте, JavaScript ищет его в прототипе, затем в прототипе прототипа и так далее. Эти ссылки образуют цепочку прототипов. Поиск останавливается на первом найденном свойстве. Если цепочка кончилась на null, а свойство не нашлось, результат — undefined, без ошибки.
const proto = Object.getPrototypeOf(rex);
console.log(Object.getPrototypeOf(proto) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
console.log(typeof rex.toString, rex.age); // function undefinedПройдём по цепочке rex снизу вверх:
- Сам объект
rex: в нём лежитname. Animal.prototype: в нём лежитgreet.Object.prototype, общий для всех объектов: в нём лежит методtoString. Поэтому он есть уrex, хотя ни класс, ни объект его не объявляли.- Дальше
null: уObject.prototypeпрототипа нет, и цепочка заканчивается.
typeof даёт "function" и для функции, и для метода, поэтому typeof rex.toString печатает function. Свойства age нет ни в одном звене, и rex.age даёт undefined.
Здесь аналогия с фабрикой перестаёт работать. Работник, не найдя инструкцию нигде, сообщил бы об ошибке, а JavaScript молча возвращает undefined.
После extends цепочка длиннее на одно звено: у объекта Dog.prototype прототип — Animal.prototype.
Методы добавляются в прототип
Свойства прототипа не копируются в объект, а ищутся при каждом чтении. Метод, добавленный в Animal.prototype позже, сразу виден и объектам, созданным раньше:
Animal.prototype.sleep = function () {
return `${this.name} sleeps`;
};
console.log(rex.sleep()); // Rex sleepsЭтот вопрос часто задают на собеседовании: «Что такое цепочка прототипов?»
Ответ: каждый объект ссылается на свой прототип, а чтение свойства идёт от объекта вверх по этим ссылкам до первого совпадения или до null.
Частая ошибка
Экземпляр скопировали через ..., а потом вызвали у копии метод:
const copy = { ...rex };
console.log(copy.name); // Rex
console.log(copy.greet()); // ✗TypeError: copy.greet is not a functionЗапись { ...rex } переносит только собственные свойства, то есть name. Новый объект — обычный, его прототип — Object.prototype, и метода greet в его цепочке нет. То же будет с structuredClone(rex): методов в самом объекте нет, поэтому ошибки DataCloneError не будет, но копия получит только данные. Копию с методами создают через класс: new Animal(rex.name).
Главное
- Собственные свойства лежат в самом объекте, а методы класса — в его прототипе
Class.prototype. - Чтение свойства идёт по цепочке прототипов снизу вверх до первого совпадения или до
null, тогда результат —undefined. - Свойства прототипа не копируются: метод, добавленный позже, виден всем экземплярам.
- Копия через
...получает только собственные свойства, без методов класса.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.