Пять способов наследования (Rect ← Square)
До
class extendsсуществовало несколько способов сцепить прототипы двух конструкторов. Все они сводятся к одному: установитьSquare.prototype.[[Prototype Pattern]] === Rect.prototype. Знание всех пяти нужно для чтения legacy-кода.
«Веб — штука архаичная, и нельзя его сломать. Всё, что работало в JavaScript 20 лет назад, должно прекрасно работать сейчас. Поэтому первый, самый старый метод до сих пор очень сильно оптимизируется», SzaXTW2qcJE.
Способ 1: древний — new Rect в prototype
function Rect(w, h) { this.w = w; this.h = h; }
function Square(s) { Rect.call(this, s, s); }
Square.prototype = new Rect(); // создаём инстанс предка как прототип
Square.prototype.constructor = Square; // восстанавливаем затёртый constructor
- Затирает дефолтный
Square.prototype - Нужно восстанавливать
constructor— иначе он будет ссылаться на Rect - Хорошо оптимизирован движками из-за древности (V8 знает паттерн)
- Минус: запускаем
new Rectбез аргументов — поля undefined
Способ 2: Object.create(Rect.prototype)
Square.prototype = Object.create(Rect.prototype);
Square.prototype.constructor = Square;
- Не вызываем
new Rect— нет побочного эффекта - Прежний
Square.prototypeосиротеет и будет собран GC - Тоже нужно восстанавливать
constructor
Способ 3: патчинг __proto__
Square.prototype.__proto__ = Rect.prototype;
- Не создаём новый
Square.prototype, а патчим существующий constructorсохраняется — не нужно восстанавливать__proto__устарел, медленнее, ломает скрытые классы
Способ 4: Object.setPrototypeOf ⭐ рекомендованный
Object.setPrototypeOf(Square.prototype, Rect.prototype);
- Часть стандарта (ES6+)
- То же, что
__proto__, но через вызов метода — корректнее - Тоже сохраняет
constructor - Минус: после установки тоже ломает скрытые классы (вызывать до первого инстанса)
Способ 5: util.inherits (только Node.js)
const util = require('util');
util.inherits(Square, Rect);
- Внутри вызывает
Object.setPrototypeOf - Читабельный сахар — не нужно рефакторить старый код
- Node-специфичный (нет в браузере)
Способ 6 (бонус): class extends — современный
class Square extends Rect {
constructor(s) { super(s, s); }
}
- Сразу сцеплено, ничего восстанавливать не надо
- Создаёт две цепочки (особенность extends — см. ниже)
Две цепочки у class extends
class Rect {}
class Square extends Rect {}
// Цепочка инстансов (для методов)
Object.getPrototypeOf(Square.prototype) === Rect.prototype; // true
// Цепочка КЛАССОВ (для статики)
Square.__proto__ === Rect; // true
Square.__proto__ === Function.prototype; // false — отличие от function-конструкторов!
С обычными function-конструкторами: Square.__proto__ === Function.prototype (стандартная цепочка функций). С class extends Rect: Square.__proto__ === Rect — наследуются и статические методы.
Порядок операций
«Сцеплять прототипы нужно до добавления методов наследнику. Если добавишь
Square.prototype.method = ...сначала, а потом сделаешьSquare.prototype = new Rect— метод затрётся».
Шпаргалка
| Способ | constructor | Современность | Где применять |
|---|---|---|---|
1. new Rect |
❌ восстанавливать | legacy | чтение старого кода |
2. Object.create |
❌ восстанавливать | до ES6 | до class extends |
3. __proto__ = |
✅ сохраняется | устарел | не использовать |
4. Object.setPrototypeOf |
✅ сохраняется | ES6 | прототипный стиль |
5. util.inherits |
✅ | Node | legacy Node |
6. class extends |
✅ | ES6+ | по умолчанию |
Источники
- Timur · Прототипное программирование и прототипное наследование (2019-11-19) — все 5 способов наглядно
- Timur · ООП: наследование и полиморфизм в JavaScript (2020-03-03)