Почему наследование важно
В JavaScript наследование — один из краеугольных камней переиспользования кода и управления сложными объектными отношениями. Язык строится на прототипной модели, а не классической, что даёт нам целый набор интересных способов организовать иерархию объектов. Каждый паттерн имеет свои достоинства и подводные камни.
1. Наследование цепочки прототипов
Самый прямолинейный способ: объект наследует свойства и методы через цепь, построенную на прототипах.
function Animal() {
this.species = 'Mammal';
this.habits = ['sleep', 'eat'];
}
function Dog() {
this.breed = 'Bulldog';
}
Dog.prototype = new Animal();
let d1 = new Dog();
let d2 = new Dog();
d1.habits.push('bark');
Плюсы:
- Простая и понятная настройка
- Подклассы сразу получают доступ ко всем методам родителя
Минусы:
- Общие ссылочные типы приводят к побочным эффектам (если изменишь массив в одном экземпляре, изменится и в других)
- Родительский конструктор вызывается для каждого создаваемого объекта
2. Наследование конструктора
Дочерний объект вызывает конструктор родителя напрямую, через call() или apply().
function Animal(species) {
this.species = species;
this.activities = [];
}
function Dog(breed) {
Animal.call(this, 'Mammal');
this.breed = breed;
}
Плюсы:
- Каждый экземпляр получает собственные свойства (нет общих ссылок)
- Можно передавать параметры в родительский конструктор
Минусы:
- Методы не переиспользуются в экземплярах — каждый раз создаются новые
- Потенциальная избыточность памяти
3. Композитное наследование
Объединяет оба предыдущих подхода: берёшь цепочку прототипов для методов и наследование конструктора для свойств.
function Animal(species) {
this.species = species;
this.activities = [];
}
Animal.prototype.getSpecies = function() {
return this.species;
};
function Dog(breed) {
Animal.call(this, 'Mammal');
this.breed = breed;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Плюсы:
- Подклассы получают собственные свойства и доступ к методам прототипа
- Хороший баланс между функциональностью и переиспользованием
Минусы:
- Родительский конструктор вызывается дважды (при вызове родителя в конструкторе дочернего и при создании прототипа), что увеличивает накладные расходы
4. Паразитное наследование
Объект создаётся, изменяется и возвращается. Гибко, но сложнее.
function Animal() {
this.species = 'Mammal';
this.habits = ['eat', 'sleep'];
}
function Dog() {
Animal.call(this);
this.breed = 'Bulldog';
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Плюсы:
- Позволяет гибко расширять объекты
- Работает с клонированными прототипами
Минусы:
- Менее подходит для больших и сложных иерархий наследования
5. Наследование прототипа
Используешь Object.create() для создания объекта с конкретным прототипом, вообще без конструкторов.
let animal = {
species: 'Mammal',
traits: ['warm-blooded', 'vertebrate']
};
function createClone(obj) {
let clone = Object.create(obj);
clone.getTraits = function() {
return this.traits;
};
return clone;
}
let dog = createClone(animal);
Плюсы:
- Простота и ясность кода
- Нет нужды в конструкторах
Минусы:
- Все экземпляры имеют общие свойства прототипа (снова проблема с ссылочными типами)
6. Паразитно-композиционное наследование
Уулучшенная версия композитного наследования: избегаешь двойного вызова конструктора через Object.create().
function Animal(species) {
this.species = species;
this.traits = [];
}
function Dog(breed) {
Animal.call(this, 'Mammal');
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Плюсы:
- Решает проблему двойного вызова конструктора
- Сохраняет все преимущества композитного наследования
- Более производительно
Минусы:
- Код становится сложнее
- Требует понимания, как работает
Object.create()
7. Наследование классов ES6
Современный синтаксис class и ключевое слово extends делают всё это гораздо понятнее.
class Animal {
constructor(species) {
this.species = species;
}
getSpecies() {
return this.species;
}
}
class Dog extends Animal {
constructor(breed) {
super('Mammal');
this.breed = breed;
}
}
Плюсы:
- Читаемый и знакомый синтаксис
- Избегаешь всех подводных камней предыдущих паттернов
- Стандарт, поддерживаемый всеми современными браузерами
Минусы:
- Под капотом это всё ещё прототипное наследование (просто красиво обёрнуто)
Что выбрать?
Для нового кода — ES6 классы. Они понятны, безопасны и стандартны. Если работаешь с наследованием в старом коде или понимаешь, что классы — это просто синтаксический сахар над прототипами, то паразитно-композиционное наследование — лучший традиционный подход: без побочных эффектов и лишних вызовов конструктора.
Выбор паттерна зависит от задачи, требований к производительности и того, насколько сложной будет иерархия объектов. Но в большинстве случаев ES6 классы — это оптимальный выбор.

