Наследование классов
Наследование классов
Технический фундамент прототипной цепочки
Наследование в JS строится через цепочку прототипов:
- методы дочернего класса ищутся сначала в нем, затем в родителе;
extendsсвязывает прототипы автоматически;super(...)иsuper.method()дают доступ к родительской логике;- при override нужно сохранять ожидаемый контракт базового метода.
Из-за этого важно проектировать базовый класс как устойчивый контракт, а не как "свалку общей логики".
Зачем нужно наследование
В проекте часто есть сущности, у которых много общего, но есть и отличия. Вместо копирования одного и того же кода в разные классы можно выделить базовый класс и расширять его через наследование.
Ключевой момент: наследование позволяет переиспользовать общую логику и добавлять специализированное поведение в дочерних классах.
Проверь себя: почему копипаст одинаковых методов в 5 классах повышает риск багов при изменениях?
Базовый синтаксис extends и super
class User {
constructor(name) {
this.name = name;
}
getLabel() {
return `User: ${this.name}`;
}
}
class Admin extends User {
constructor(name, level) {
super(name);
this.level = level;
}
}
const admin = new Admin('Оля', 2);
console.log(admin.getLabel());
Что важно:
extendsсвязывает дочерний класс с базовым;super(...)в конструкторе вызывает конструктор родителя;- в дочернем классе
super(...)нужно вызвать доthis.
Здесь часто путаются: если в дочернем классе есть constructor, но ты не вызвал super, будет ошибка.
Переопределение методов (override)
Дочерний класс может изменить поведение метода родителя.
class User {
getRole() {
return 'user';
}
}
class Admin extends User {
getRole() {
return 'admin';
}
}
Дополнительный пример: расширение родительского метода через super.
class User {
getPermissions() {
return ['read'];
}
}
class Admin extends User {
getPermissions() {
return [...super.getPermissions(), 'write', 'delete'];
}
}
Это называется переопределением метода.
Можно также использовать super.methodName() внутри переопределенного метода, чтобы расширить базовое поведение, а не полностью заменить его.
Реальные микро-сценарии
- Пользователи в системе доступа.
User- базовые свойства и методы.Admin- дополнительные права.Moderator- специфичные действия модерации.
- Платежные методы.
PaymentMethod- общий интерфейс оплаты.CardPayment,CryptoPayment- разные реализации с общей базой.
- UI-компоненты.
BaseComponentс общимиmount/unmount.Modal,Tooltip,Dropdownрасширяют базовый класс.
Смотри, что важно: наследование полезно, когда есть реальное отношение "является" (Admin является User).
Когда наследование превращается в проблему
Слишком глубокая иерархия (A -> B -> C -> D...) усложняет понимание и отладку. Изменение в базовом классе может неожиданно повлиять на множество потомков.
Анти-провал:
- не делать иерархию ради иерархии;
- избегать слишком универсальных "бог-классов" в основании;
- для части задач использовать композицию (сборку из отдельных объектов/функций), если это проще.
Проверь себя: если классы не имеют естественного отношения "is-a", стоит ли их связывать через extends?
Частые ошибки новичков
- Забывать
super(...)в конструкторе наследника. - Переопределять метод и ломать ожидаемый контракт (другой тип результата).
- Наследоваться только ради "экономии строк", не думая о модели предметной области.
- Добавлять слишком много логики в базовый класс, делая его хрупким.
Что будет, если изменить входные данные
Если в new Admin(name, level) передать пустое name, а валидации нет, ошибка проявится позже в разных методах. Поэтому базовый класс должен задавать устойчивый контракт и проверять критичные поля.
Проверь себя: где логичнее проверять имя - в базовом User или отдельно в каждом наследнике?
Краткий итог
- Наследование (
extends) позволяет переиспользовать общую логику базового класса. superнужен для вызова логики родителя, особенно в конструкторе.- Переопределение методов дает специализированное поведение для дочерних классов.
- Наследование полезно только при естественной связи "является".
- Слишком глубокие иерархии усложняют код, поэтому используй наследование осознанно.