Ключове слово 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може стояти будь-який вираз, що повертає конструктор: клас, функція-конструктор або результат виклику фабрики.
Швидкий приклад
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 робить під капотом
Коли ви пишете:
class B extends A {}рушій виконує еквівалент двох операцій:
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 будує одразу два різні ланцюги. Для екземпляра:
// instance of B -> B.prototype -> A.prototype -> Object.prototype -> nullІ окремий ланцюг для самих класів, які є об'єктами-функціями:
// B -> A -> Function.prototype -> Object.prototype -> nullКонструктор нащадка і super()
Клас-нащадок може викликати конструктор батька через super().
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().
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 члени, вони теж успадковуються:
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.
Успадкування від функцій і динамічних виразів
Успадковувати можна не лише класи, а й звичайні функції-конструктори:
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.
Вираз праворуч обчислюється в момент оголошення класу, тому базовий клас можна обрати динамічно:
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.