Пять способов наследования (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+ по умолчанию

Источники

Связанные темы