Перейти к содержимому
Теория · Классы и прототипы · урок 5 из 12

Наследование

Вернёмся на мебельную фабрику. Ей понадобилось кресло-качалка. Рисовать его с нуля незачем: это тот же стул, только с полозьями. Инженер копирует готовый чертёж стула и дописывает на копии одно отличие.

С классами бывает так же. У собаки и кошки есть общее: имя и приветствие. Если завести для них два отдельных класса, одинаковый код придётся написать дважды. Вместо этого класс может взять свойства и методы у другого класса:

JavaScript
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 их нет.

Что происходит в примере:

  1. Своего constructor у Dog нет, поэтому new Dog("Rex") вызывает constructor родителя, и тот записывает name.
  2. 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(…) в нём обязателен:

JavaScript
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 работает в два шага:

  1. super(name) запускает конструктор Animal, и тот записывает в объект name.
  2. 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() — в его метод.

Вживуюnew Cat и super по шагамшаг 1 из 9 · рамок: 1

new Cat("Tom", 9) запускает конструктор Cat.

Каждая рамка — незавершённый вызов, верхняя выполняется сейчас

На собеседовании любят спрашивать, почему super(…) вызывают до this. Ответ: пока super(…) не вызван, объекта ещё нет — его создаёт конструктор родителя.

Частая ошибка

В constructor дочернего класса обратились к this раньше, чем вызвали super:

JavaScript
class Bird extends Animal {
    constructor(name) {
        this.wings = 2; // ✗
        super(name);
    }
}

const kesha = new Bird("Kesha");
TEXT
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 и подпишись на канал — откроются все уроки.

Войти другим способом