Прототипное наследование и классы: как устроена цепочка

Как JavaScript ищет свойства и почему class — это «обёртка» над прототипами.

Цепочка прототипов — механизм, по которому объект, не найдя свойство у себя, ищет его у своего прототипа, затем у прототипа прототипа, и так до null.

__proto__ против prototype

Их постоянно путают. prototype — свойство функции-конструктора: объект, который станет прототипом для созданных экземпляров. __proto__ — ссылка экземпляра на его прототип.

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return this.name + " издаёт звук";
};

const dog = new Animal("Рекс");
console.log(dog.speak());                                  // метод найден в прототипе
console.log(Object.getPrototypeOf(dog) === Animal.prototype);
console.log(dog.hasOwnProperty("name"));                   // своё свойство
console.log(dog.hasOwnProperty("speak"));                  // не своё, из прототипа

Вывод:

Рекс издаёт звук
true
true
false

Свойство name лежит на самом объекте, а метод speak — на прототипе. При вызове dog.speak() движок не находит speak у dog и поднимается по цепочке к Animal.prototype.

Цепочка наследования

Наследование делают, связывая прототипы. Экземпляр получает доступ к методам обоих уровней.

function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return this.name + " издаёт звук"; };

function Dog(name) { Animal.call(this, name); } // вызвали «родителя»
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () { return this.name + " лает"; };

const d = new Dog("Шарик");
console.log(d.bark());            // свой метод
console.log(d.speak());           // унаследованный
console.log(d instanceof Dog);
console.log(d instanceof Animal); // вся цепочка

Вывод:

Шарик лает
Шарик издаёт звук
true
true

Классы — синтаксический сахар

Ключевое слово class не добавляет новой модели наследования: под капотом — те же прототипы. Тот же пример короче и читаемее.

class Animal {
  constructor(name) { this.name = name; }
  speak() { return this.name + " издаёт звук"; }
}

class Dog extends Animal {
  bark() { return this.name + " лает"; }
}

const d = new Dog("Шарик");
console.log(d.bark());
console.log(d.speak());
console.log(typeof Dog);                    // на самом деле функция
console.log(d.speak === Dog.prototype.speak ||
            d.speak === Animal.prototype.speak);

Вывод:

Шарик лает
Шарик издаёт звук
function
true

typeof Dog === "function" доказывает: класс — это функция-конструктор, а методы лежат в prototype. Это любимое «разоблачение» на собеседовании.

Итог

  • Поиск свойства идёт по цепочке прототипов до null.
  • prototype — у конструктора, __proto__ — у экземпляра.
  • class — синтаксический сахар: внутри те же прототипы и функции-конструкторы.
Проверьте себя
1. Где движок ищет метод, которого нет у самого объекта?
AНигде, сразу вернёт undefined
BПоднимается по цепочке прототипов до null
CВ глобальном объекте
DВ ближайшей функции
2. Чем prototype отличается от __proto__?
AЭто одно и то же
Bprototype — свойство конструктора, __proto__ — ссылка экземпляра на прототип
C__proto__ есть только у функций
Dprototype есть только у массивов
3. Чем являются классы (class) в JavaScript под капотом?
AНовой моделью наследования, не связанной с прототипами
BСинтаксическим сахаром над функциями-конструкторами и прототипами
CОбъектами без методов
DКопиями из других языков без реализации
Поддержать проект