Как работает прототипное наследование в JavaScript?

SeniorJavaScript #javascript #prototypes #oop

Вопрос

Как устроено прототипное наследование в JavaScript?

Короткий ответ

В отличие от классических ООП-языков, JavaScript использует прототипы: каждый объект имеет внутреннюю ссылку [[Prototype]] на другой объект, у которого ищутся свойства/методы, если их нет у самого объекта.

Подробный ответ

  • У каждой функции есть свойство prototype — объект, который станет [[Prototype]] (доступен как __proto__ или через Object.getPrototypeOf) экземпляров, созданных через new.
  • Поиск свойства идёт по цепочке прототипов: объект → его прототип → прототип прототипа → ... → null.
  • Синтаксис class в ES6 — это синтаксический сахар над этим же механизмом: class Dog extends Animal под капотом настраивает цепочку прототипов ровно так же, как Dog.prototype = Object.create(Animal.prototype).
  • Object.create(proto) создаёт объект с явно заданным прототипом без вызова конструктора — самый "чистый" способ увидеть механизм в деле.

Практическое следствие: методы стоит определять на 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.speak = function () {
  return `${this.name} лает`
}

const rex = new Dog('Рекс')
console.log(rex.speak()) // "Рекс лает"
console.log(rex instanceof Animal) // true — Dog.prototype наследует от Animal.prototype

// То же самое через class:
class AnimalEs6 {
  constructor(name) { this.name = name }
  speak() { return `${this.name} издаёт звук` }
}
class DogEs6 extends AnimalEs6 {
  speak() { return `${this.name} лает` }
}

Дополнительные вопросы

  • Чем __proto__ отличается от prototype?
  • Как работает instanceof изнутри?
  • Почему методы лучше определять на прототипе, а не в конструкторе?