Наследование классов
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Классы редко живут поодиночке: один описывает общее, другой — частный случай. extends связывает их так, что потомок бесплатно получает всё поведение родителя и дописывает поверх своё.
Запись class Dog extends Animal говорит движку: если у Dog метода нет — ищи его у Animal. Технически прототип Dog.prototype начинает указывать на Animal.prototype, и поиск метода просто идёт на одну ступень выше.
У Dog нет ни одной собственной строки, но объект уже умеет всё, что умел родитель. Это и есть главный смысл наследования: не переписывать общее, а уточнять частное.
extends тянет и статику: Dog.create() найдётся, если он объявлен у Animal.
Array, Map, Error — обычные классы, их тоже можно расширять. Методы вроде map при этом возвращают экземпляр вашего класса, а не голый массив.
Если у потомка есть свой constructor, он обязан вызвать super(...) — и сделать это раньше любого обращения к this. Причина честнее, чем «так принято»: конструктор потомка не создаёт объект сам. Объект создаёт конструктор родителя, а super() — это и есть момент его создания.
Переставьте строки местами — и получите ошибку. До super() переменной this просто не существует, и обращение к ней падает так же, как к переменной до её объявления.
Если конструктор потомку не нужен, его можно не писать вовсе — движок подставит невидимый constructor(...args) { super(...args); }, который передаст родителю все аргументы как есть.
Метод потомка с тем же именем полностью закрывает собой родительский. Но чаще нужно не «вместо», а «поверх»: взять результат родителя и что-то с ним сделать. Для этого и есть super.метод() — вызов родительской версии с текущим this.
Обратите внимание: super.format — это не «метод родительского объекта», отдельного объекта-родителя не существует. Это поиск метода на ступень выше по цепочке прототипов, но выполняется он на том же самом экземпляре.
Куда смотрит super, решается один раз — в момент объявления метода. Метод запоминает объект, в котором он определён, поэтому super продолжает работать, даже если вызвать метод с чужим this.
Побочный вывод: super работает только в методе, объявленном сокращённым синтаксисом, а не в обычной функции, присвоенной свойству. Стрелочные функции внутри метода super видят — они берут его из окружающего метода.
Поля класса (value = 1 прямо в теле) выглядят просто, но у них есть свой момент выполнения. У родителя они срабатывают в начале его конструктора, у потомка — сразу после возврата из super(). Значит, поле потомка перезаписывает одноимённое поле родителя.
Отсюда популярная ловушка: конструктор родителя вызывает метод, который потомок переопределил, а тот опирается на поле потомка. Поля ещё не проинициализированы — получаем undefined.
| Шаг | Что происходит |
|---|---|
| 1 | Вызван конструктор потомка, this ещё не существует |
| 2 | super(...) запускает конструктор родителя |
| 3 | Поля родителя получают значения |
| 4 | Выполняется тело конструктора родителя |
| 5 | Поля потомка получают значения |
| 6 | Выполняется остаток конструктора потомка |
Практический вывод: не вызывайте из конструктора родителя методы, которые потомок может переопределить. Если без этого никак — вынесите такой вызов в отдельный метод init(), который потомок дёрнет сам, когда будет готов.
Наследование — самая жёсткая связь между классами: потомок зависит от каждой детали родителя, включая те, что ему не нужны. Проверка простая. Если про потомка можно сказать «это разновидность родителя» — наследование уместно. Если получается «внутри него есть родитель» — нужна композиция.
Композиция — это когда объект не наследует умение, а держит внутри другой объект, который умеет. Меняется только поле, а не форма всего дерева классов.
Вариант Car extends Engine тоже «заработал бы»: машина получила бы start(). Но заодно и всё остальное содержимое мотора, а поменять мотор на электрический стало бы невозможно без переписывания класса. Наследование стоит брать, когда общее поведение действительно одно на всех и меняться не будет; в остальных случаях сначала пробуйте композицию.
class B extends Aсвязывает прототипы: чего нет у B, ищется у A — включая статические методы- Собственный конструктор потомка обязан вызвать
super(...)до любогоthis: объект создаёт конструктор родителя. Без своего конструктора подставляется неявныйconstructor(...args) { super(...args); } super.метод()вызывает родительскую версию на текущем экземпляре — удобно, когда нужно «поверх», а не «вместо»- Поля потомка инициализируются только после возврата из
super(), поэтому конструктору родителя нельзя звать переопределяемые методы - «Это разновидность» — наследование, «внутри него есть» — композиция; вторая гибче и почти всегда безопаснее
1/3Что произойдёт, если в конструкторе потомка обратиться к this до вызова super()?
2/3Класс Child extends Base не объявляет собственный конструктор. Что случится при new Child(1, 2)?
3/3Что делает super.render() внутри метода класса-потомка?
Товар со скидкой
Есть класс Product. Напишите DiscountProduct, который принимает третьим аргументом процент скидки, хранит уже уменьшенную цену и дописывает к подписи пометку о скидке.
Цену пересчитываем прямо в аргументе super() — обращения к this там нет, поэтому так можно. Своё поле percent записываем уже после вызова.
Метод label не переписывает формат подписи заново, а берёт готовую строку у родителя через super.label() и дополняет её. Изменится формат в Product — потомок подхватит изменение сам.
Из наследования в композицию
Уведомления начали с наследования: EmailNotifier extends Notifier, рядом появился SmsNotifier. Теперь нужен вариант, который шлёт сразу в оба канала, — и дерево классов ломается. Переделайте на композицию так, чтобы набор каналов задавался при создании.
Канал — не «разновидность уведомителя», а его деталь. Значит, каждый канал становится отдельным объектом, а Notifier просто хранит их список.
Добавить третий канал теперь — одна строка в массиве, а не новый класс в дереве. И комбинация каналов больше не требует отдельного класса на каждое сочетание.