Перейти к содержимому
Теория · Классы и прототипы · урок 7 из 12

Цепочка прототипов

Вернёмся на мебельную фабрику. Инструкцию «как сложить стул» не приклеивают к каждому стулу: она лежит в папке чертежа, одна на все стулья. Если работник не нашёл ответа на самом стуле, он открывает папку его чертежа. Не нашёл и там — берёт общий справочник фабрики, один на всю мебель.

Объекты в JavaScript ищут свойства точно так же. Node.js печатает экземпляр класса только с его данными: в Animal { name: 'Rex' } метода greet не видно. И всё же вызов rex.greet() работает. Метод лежит не в самом объекте:

JavaScript
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

Давайте разберём три строки вывода:

  1. console.log(rex) показывает только name — данные самого объекта.
  2. Object.hasOwn подтверждает: name в объекте есть, а greet нет.
  3. Последняя строка печатает true: прототип rex — объект Animal.prototype. Это и есть папка чертежа.

Собственные свойства

Функция Object.hasOwn(obj, key) проверяет, есть ли у объекта собственное свойство с таким ключом. Такое свойство записано в сам объект. Свойство name записал constructor, а greet в объекте нет.

Прототип

Каждый объект хранит ссылку на свой прототип: другой объект или null. Получить его можно функцией Object.getPrototypeOf.

У каждого класса есть свойство prototype: в нём лежит объект, который new делает прототипом новых экземпляров. У экземпляров класса Animal прототип — объект Animal.prototype, и в нём лежат методы из тела класса, кроме статических. Поэтому метод один на все экземпляры.

Поиск по цепочке

Если свойства нет в самом объекте, JavaScript ищет его в прототипе, затем в прототипе прототипа и так далее. Эти ссылки образуют цепочку прототипов. Поиск останавливается на первом найденном свойстве. Если цепочка кончилась на null, а свойство не нашлось, результат — undefined, без ошибки.

JavaScript
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 снизу вверх:

  1. Сам объект rex: в нём лежит name.
  2. Animal.prototype: в нём лежит greet.
  3. Object.prototype, общий для всех объектов: в нём лежит метод toString. Поэтому он есть у rex, хотя ни класс, ни объект его не объявляли.
  4. Дальше null: у Object.prototype прототипа нет, и цепочка заканчивается.

typeof даёт "function" и для функции, и для метода, поэтому typeof rex.toString печатает function. Свойства age нет ни в одном звене, и rex.age даёт undefined.

Здесь аналогия с фабрикой перестаёт работать. Работник, не найдя инструкцию нигде, сообщил бы об ошибке, а JavaScript молча возвращает undefined.

После extends цепочка длиннее на одно звено: у объекта Dog.prototype прототип — Animal.prototype.

Методы добавляются в прототип

Свойства прототипа не копируются в объект, а ищутся при каждом чтении. Метод, добавленный в Animal.prototype позже, сразу виден и объектам, созданным раньше:

JavaScript
Animal.prototype.sleep = function () {
    return `${this.name} sleeps`;
};

console.log(rex.sleep()); // Rex sleeps

Этот вопрос часто задают на собеседовании: «Что такое цепочка прототипов?»

Ответ: каждый объект ссылается на свой прототип, а чтение свойства идёт от объекта вверх по этим ссылкам до первого совпадения или до null.

Частая ошибка

Экземпляр скопировали через ..., а потом вызвали у копии метод:

JavaScript
const copy = { ...rex };
console.log(copy.name); // Rex
console.log(copy.greet()); // ✗
TEXT
TypeError: copy.greet is not a function

Запись { ...rex } переносит только собственные свойства, то есть name. Новый объект — обычный, его прототип — Object.prototype, и метода greet в его цепочке нет. То же будет с structuredClone(rex): методов в самом объекте нет, поэтому ошибки DataCloneError не будет, но копия получит только данные. Копию с методами создают через класс: new Animal(rex.name).

Главное

  • Собственные свойства лежат в самом объекте, а методы класса — в его прототипе Class.prototype.
  • Чтение свойства идёт по цепочке прототипов снизу вверх до первого совпадения или до null, тогда результат — undefined.
  • Свойства прототипа не копируются: метод, добавленный позже, виден всем экземплярам.
  • Копия через ... получает только собственные свойства, без методов класса.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом