Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ключове слово extends». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`extends` ставить один клас у прототипний ланцюг іншого.** Запис `class B extends A {}` зв'язує `B.prototype` з `A.prototype` (це дає методи екземплярів) і сам `B` з `A` (це дає статичні члени), тож нащадок отримує і методи, і статику батька. Конструктор нащадка викликає батьківський через `super()`, а перевизначений метод дістається до батьківської реалізації через `super.methodName()`. ```javascript class Dog extends Animal {} ``` **Ключове:** `extends` створює два прототипні зв'язки одразу: `B.prototype.__proto__ = A.prototype` і `Object.setPrototypeOf(B, A)`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.