Наследование
Вернёмся на мебельную фабрику. Ей понадобилось кресло-качалка. Рисовать его с нуля незачем: это тот же стул, только с полозьями. Инженер копирует готовый чертёж стула и дописывает на копии одно отличие.
С классами бывает так же. У собаки и кошки есть общее: имя и приветствие. Если завести для них два отдельных класса, одинаковый код придётся написать дважды. Вместо этого класс может взять свойства и методы у другого класса:
class Animal {
constructor(name) {
this.name = name;
}
greet() {
return `I am ${this.name}`;
}
}
class Dog extends Animal {}
const rex = new Dog("Rex");
console.log(rex.greet()); // I am Rex
console.log(rex instanceof Dog, rex instanceof Animal); // true trueТело класса Dog пустое, и всё же rex.greet() печатает I am Rex. Давайте разберём, откуда у собаки имя и приветствие.
extends
Запись class Dog extends Animal создаёт дочерний класс Dog от класса Animal. Animal для него — родительский класс, или родитель. Это наследование: экземпляр Dog получает все методы Animal, хотя в теле Dog их нет.
Что происходит в примере:
- Своего
constructorуDogнет, поэтомуnew Dog("Rex")вызываетconstructorродителя, и тот записываетname. rex.greet()находит методgreetу родителя и печатаетI am Rex.
Цепочка может быть длиннее: class Puppy extends Dog {} получит методы и Dog, и Animal.
Здесь аналогия с чертежом перестаёт работать. Инженер копирует чертёж на новый лист, а Dog ничего у Animal не копирует: его экземпляры находят методы родителя заново при каждом вызове.
instanceof
Оператор instanceof проверяет, создан ли объект этим классом или его дочерним классом. rex — экземпляр и Dog, и Animal, поэтому оба выражения дают true. Кресло-качалка тоже остаётся стулом: на нём можно сидеть.
Свой constructor и super
Если дочернему классу нужны свои свойства, он объявляет свой constructor. Первым делом тот вызывает super(…) — конструктор родителя с нужными аргументами. Только после этого можно обращаться к this. Если constructor объявлен в дочернем классе, вызов super(…) в нём обязателен:
class Cat extends Animal {
constructor(name, lives) {
super(name);
this.lives = lives;
}
greet() {
return `${super.greet()}, ${this.lives} lives`;
}
}
const tom = new Cat("Tom", 9);
console.log(tom.greet()); // I am Tom, 9 lives
console.log(rex.greet()); // I am RexКонструктор Cat работает в два шага:
super(name)запускает конструкторAnimal, и тот записывает в объектname.this.lives = livesдописывает свойство, которого у родителя нет.
Переопределение метода
У Cat есть свой метод greet, и для кошек он заменяет одноимённый метод Animal. Это переопределение. Вызов tom.greet() запускает версию Cat, а экземпляры Animal и Dog по-прежнему вызывают greet из Animal.
Внутри переопределённого метода super.greet() вызывает версию родителя. Здесь она возвращает "I am Tom", а метод Cat дописывает к ней число жизней. Вызов this.greet() на этом месте снова запустил бы метод Cat. Метод вызывал бы сам себя без конца, и программа остановилась бы с RangeError.
Труднее всего в этой теме уследить, в каком порядке работают код родителя и дочернего класса. Нажимайте «Следующий шаг» и следите за стопкой вызовов: сначала super(name) уходит в конструктор Animal, потом super.greet() — в его метод.
1class Animal {2 constructor(name) {3 this.name = name;4 }5 greet() {6 return `I am ${this.name}`;7 }8}9 10class Cat extends Animal {11 constructor(name, lives) {12 super(name);13 this.lives = lives;14 }15 greet() {16 const s = super.greet();17 return `${s}, ${this.lives} lives`;18 }19}20 21const tom = new Cat("Tom", 9);22console.log(tom.greet());
new Cat("Tom", 9) запускает конструктор Cat.
На собеседовании любят спрашивать, почему super(…) вызывают до this. Ответ: пока super(…) не вызван, объекта ещё нет — его создаёт конструктор родителя.
Частая ошибка
В constructor дочернего класса обратились к this раньше, чем вызвали super:
class Bird extends Animal {
constructor(name) {
this.wings = 2; // ✗
super(name);
}
}
const kesha = new Bird("Kesha");ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorЭто сообщение Node.js и Chrome. Ошибка появляется при создании объекта через new, а не при объявлении класса. Пока super не вызван, объекта ещё нет: его создаёт конструктор родителя. Поэтому super(name) ставят первой строкой, а свойства записывают после него.
Главное
class B extends AделаетBдочерним классом: его экземпляры получают методыA, иinstanceof Aдля них даётtrue.- Свой
constructorдочернего класса вызываетsuper(…)раньше любого обращения кthis. - Метод с тем же именем в дочернем классе переопределяет метод родителя.
super.method()вызывает версию родителя, аthis.method()— снова свою.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.