Skip to main content

Ключове слово extends

extends створює клас-нащадок і зв'язує його з батьківським класом через прототипи: екземпляри нащадка отримують методи батька, а сам клас-нащадок отримує його статичні члени. Разом із super() це і є повний механізм успадкування в ES6-класах.

Теорія

TL;DR

  • class B extends A {} виконує два зв'язування: B.prototype.__proto__ = A.prototype і Object.setPrototypeOf(B, A).
  • Перший зв'язок дає успадкування методів екземпляра, другий дає успадкування статичних методів і полів.
  • Якщо в нащадку оголошено власний constructor, він зобов'язаний викликати super() раніше за перше звертання до this.
  • Перевизначений метод може викликати батьківську реалізацію через super.methodName().
  • Праворуч від extends може стояти будь-який вираз, що повертає конструктор: клас, функція-конструктор або результат виклику фабрики.

Швидкий приклад

javascript
class Animal { speak() { console.log("The animal makes a sound"); } } class Dog extends Animal { bark() { console.log("Woof-woof!"); } } const dog = new Dog(); dog.speak(); // "The animal makes a sound", the method is inherited dog.bark(); // "Woof-woof!"

Dog успадковує всі методи Animal, бо пошук іде ланцюгом Dog.prototype, далі Animal.prototype, далі Object.prototype.

Що extends робить під капотом

Коли ви пишете:

javascript
class B extends A {}

рушій виконує еквівалент двох операцій:

javascript
B.prototype.__proto__ = A.prototype; // instance methods Object.setPrototypeOf(B, A); // static methods and fields

Звідси два наслідки:

  • методи екземплярів (B.prototype.method) шукаються в A.prototype, якщо їх немає у B.prototype;
  • статичні члени (A.staticMethod) доступні і через B.

Тобто extends будує одразу два різні ланцюги. Для екземпляра:

javascript
// instance of B -> B.prototype -> A.prototype -> Object.prototype -> null

І окремий ланцюг для самих класів, які є об'єктами-функціями:

javascript
// B -> A -> Function.prototype -> Object.prototype -> null

Конструктор нащадка і super()

Клас-нащадок може викликати конструктор батька через super().

javascript
class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name, breed) { super(name); // calls the Animal constructor this.breed = breed; } } const dog = new Dog("Rex", "shepherd"); console.log(dog.name); // "Rex" console.log(dog.breed); // "shepherd"

Важливо:

  • якщо ви оголошуєте власний constructor у нащадку, виклик super() обов'язковий перед будь-яким використанням this;
  • без нього буде ReferenceError: Must call super constructor before accessing 'this';
  • якщо власного конструктора немає, рушій сам підставляє constructor(...args) { super(...args); }.

Перевизначення методів і super.methodName()

Метод батька можна перевизначити і при цьому викликати стару реалізацію через super.methodName().

javascript
class Animal { speak() { console.log("The animal speaks"); } } class Dog extends Animal { speak() { super.speak(); // calls the parent implementation console.log("The dog barks"); } } new Dog().speak(); // The animal speaks // The dog barks

Успадкування статичних методів і полів

Якщо батько має static члени, вони теж успадковуються:

javascript
class Animal { static category = "Animals"; static info() { console.log("This is the base class for animals"); } } class Dog extends Animal {} console.log(Dog.category); // "Animals" Dog.info(); // "This is the base class for animals"

Працює це саме завдяки другому зв'язку: пошук властивості Dog.category не знаходить її на самому Dog і піднімається до Animal.

Успадкування від функцій і динамічних виразів

Успадковувати можна не лише класи, а й звичайні функції-конструктори:

javascript
function Vehicle(name) { this.name = name; } Vehicle.prototype.drive = function () { console.log(`${this.name} is driving`); }; class Car extends Vehicle {} const car = new Car("BMW"); car.drive(); // "BMW is driving"

extends працює з будь-яким значенням, у якого є prototype, не обов'язково з class.

Вираз праворуч обчислюється в момент оголошення класу, тому базовий клас можна обрати динамічно:

javascript
function getBaseClass(type) { return type === "cat" ? class Animal { speak() { console.log("Meow"); } } : class Animal { speak() { console.log("Woof"); } }; } class Pet extends getBaseClass("cat") {} new Pet().speak(); // "Meow"

Це потужний прийом: саме на ньому тримаються міксини, адаптери та фабрики моделей.

Зведення того, що дає extends:

Що робить extendsОпис
Будує ланцюг прототипівChild.prototype успадковує від Parent.prototype
Дозволяє викликати конструктор батькачерез super()
Дозволяє викликати методи батькачерез super.methodName()
Успадковує статичні методи і поляChild.staticMethod()
Працює не лише з класамиа й з функціями-конструкторами
Приймає динамічні виразиclass X extends getBaseClass()

Типові помилки

  • Звертання до this у конструкторі нащадка до виклику super(). Це завжди ReferenceError, а не undefined.
  • Переконання, що статика не успадковується. Насправді Dog.info() працює без жодного додаткового коду.
  • Спроба успадкувати від стрілкової функції або від звичайного об'єкта: у них немає prototype, тому буде TypeError.
  • Виклик батьківського методу як Animal.prototype.speak.call(this) замість super.speak(). Працює, але ламається, щойно ієрархія зміниться.
  • Плутанина двох ланцюгів: Dog.__proto__ це Animal, а Dog.prototype.__proto__ це Animal.prototype.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.