Прототипное наследование #
Прототип — скрытая ссылка, по которой движок ищет свойства и методы, если их нет на самом объекте. Наследование в JavaScript прототипное: объекты делегируют доступ к другому объекту-прототипу, а не копируют поля класса как в классических ООП-языках.
[[Prototype]] и поиск свойств #
У каждого объекта есть внутренняя ссылка [[Prototype]] (в консоли часто видна как __proto__, но в коде так не пишут):
const lesson = {
title: 'Прототипы',
level: 'middle',
}
lesson.toString() // "[object Object]"
Метода toString на lesson нет — движок поднимается по цепочке к Object.prototype и находит его там.
Порядок поиска:
- собственное свойство объекта;
- свойство на прототипе;
- прототип прототипа — и так далее, пока не
null.
Функции-конструкторы и prototype #
До class объекты часто создавали через функцию с new:
function Lesson(title, minutes) {
this.title = title
this.minutes = minutes
}
Lesson.prototype.describe = function () {
return `${this.title} (~${this.minutes} мин)`
}
const intro = new Lesson('Введение', 12)
intro.describe() // "Введение (~12 мин)"
new создаёт объект, связывает его [[Prototype]] с Lesson.prototype и вызывает функцию с this, указывающим на новый объект. Методы на prototype общие для всех экземпляров — экономия памяти.
Цепочка прототипов #
const quiz = new Lesson('Контрольные вопросы', 8)
quiz.hasOwnProperty('title') // true — собственное свойство
quiz.hasOwnProperty('describe') // false — унаследовано с prototype
Lesson.prototype сам является объектом и наследует от Object.prototype. Цепочка:
quiz → Lesson.prototype → Object.prototype → null
Если свойство нигде не найдено (дошли до null), результат — undefined (для чтения) или возможность записи на сам объект.
Object.create #
Явное задание прототипа без конструктора:
const lessonBehavior = {
markComplete() {
this.done = true
},
}
const chapter = Object.create(lessonBehavior)
chapter.title = 'Глава 3'
chapter.markComplete()
chapter.done // true
Object.getPrototypeOf(chapter) === lessonBehavior // true
Object.create(proto) создаёт объект с [[Prototype]] === proto. Удобно для композиции поведения без new.
Собственные vs унаследованные #
for (const key in quiz) {
console.log(key)
}
// перечислит и title, и describe — цикл идёт по цепочке
Только собственные ключи:
Object.keys(quiz) // ['title', 'minutes']
Object.hasOwn(quiz, 'describe') // false (ES2022+)
При расширении объектов проверяйте hasOwnProperty или Object.hasOwn, чтобы не трогать унаследованные поля.
Изменение прототипа #
Прототип можно менять у функции-конструктора (влияет на будущие экземпляры) или у одного объекта:
const track = { title: 'Async' }
const proto = {
getLabel() {
return `[${this.level}] ${this.title}`
},
}
Object.setPrototypeOf(track, proto)
track.level = 'advanced'
track.getLabel() // "[advanced] Async"
Мутация Lesson.prototype после создания экземпляров добавляет методы всем уже существующим объектам этой линии — мощно, но рискованно в библиотечном коде.
class — синтаксический сахар #
class из ES2015 не отменяет прототипы — под капотом те же механизмы:
class Lesson {
constructor(title, minutes) {
this.title = title
this.minutes = minutes
}
describe() {
return `${this.title} (~${this.minutes} мин)`
}
}
typeof Lesson // "function"
Lesson.prototype.describe // функция на прототипе
Понимание прототипов помогает отлаживать this, наследование и instanceof.
Частые ошибки #
- Путаница
__proto__иprototype— у функции есть свойство.prototype; у экземпляра — ссылка[[Prototype]]на него. - Методы в конструкторе вместо prototype — каждый экземпляр получает копию функции, лишняя память.
- Перезапись
Constructor.prototypeцеликом — ломается связь с уже созданными объектами; дополняйте, а не заменяйте объект. - Ожидание «классического» копирования полей — изменение на прототипе видно всем экземплярам.
Lesson.prototype.badge = 'JSFlow'
const a = new Lesson('A', 5)
const b = new Lesson('B', 10)
a.badge // "JSFlow" — общее через прототип
Итог #
- Объекты наследуют через цепочку прототипов, а не копирование класса.
- Функция-конструктор +
prototype— классический способ;class— тот же механизм, другой синтаксис. Object.createиObject.setPrototypeOfзадают прототип явно.- Различайте собственные свойства и унаследованные при обходе и расширении объектов.