JS Advanced

Расширение встроенных классов

Понять, как наследоваться от встроенных классов (Array, Map, Error), какие ограничения есть, и когда это оправдано.

Продвинутый (Advanced)

Кратко

JavaScript позволяет наследоваться от встроенных классов: class MyArray extends Array. Это полезно для кастомных коллекций. Ограничения:Species pattern, не все built-ins расширяемы одинаково. В обычном коде расширение built-ins встречается редко.


Подробно

Расширение Array: class MyArray extends Array { get first() { return this[0]; } get last() { return this[this.length - 1]; } }

const arr = new MyArray(1, 2, 3); console.log(arr.first); // 1 console.log(arr.last); // 3 console.log(arr instanceof MyArray); // true

// Методы возвращают MyArray: const filtered = arr.filter(x => x > 1); console.log(filtered instanceof MyArray); // true console.log(filtered.first); // 2

Species pattern: class MyError extends Error { constructor(message) { super(message); this.name = 'MyError'; } }

// По умолчанию: MyError[Symbol.species] = MyError // Это значит: методы Error будут возвращать MyError

Расширение Map: class DurationMap extends Map { setWithTimeout(key, value, ms) { this.set(key, value); setTimeout(() => this.delete(key), ms); } }

Ограничения:

  • Symbol.species может изменять поведение методов
  • Не все built-ins имеют одинаковую поддержку
  • Некоторые встроенные методы могут не работать корректно

Ментальная модель

Расширение built-ins — как создать кастомный инструмент на основе стандартного. Например: MyArray — это Array с дополнительными кнопками (first, last). Стандартные функции (filter, map) работают и возвращают MyArray, потому что JavaScript использует Species pattern.


Примеры

**Самый простой:**

javascript
class CountedArray extends Array {
  get count() {
    return this.length;
  }
}

const arr = new CountedArray(1, 2, 3);
console.log(arr.count); // 3

Простейшее расширение Array с собственным методом.

**Простой:**

javascript
class TaggedError extends Error {
  constructor(tag, message) {
    super(message);
    this.tag = tag;
    this.name = 'TaggedError';
  }
}

const err = new TaggedError('AUTH', 'Unauthorized');
console.log(err.tag); // 'AUTH'
console.log(err instanceof Error); // true

Расширение Error для кастомных ошибок.

**Реальный пример:**

javascript
class PriorityQueue extends Array {
  enqueue(item, priority) {
    this.push({ item, priority });
    this.sort((a, b) => a.priority - b.priority);
  }

  dequeue() {
    return this.shift()?.item;
  }
}

const pq = new PriorityQueue();
pq.enqueue('low', 3);
pq.enqueue('high', 1);
pq.enqueue('medium', 2);

console.log(pq.dequeue()); // 'high'
console.log(pq.dequeue()); // 'medium'

Расширение Array для Priority Queue.


Частые ошибки

**Неправильно:** Расширять Array без понимания Species

**Почему:** Методы Array (filter, map) возвращают экземпляр родителя — это может нарушить ожидания.

**Правильно:** Понимайте Symbol.species и его влияние на возвращаемые типы.

**Неправильно:** Думать, что расширение built-ins — обычный паттерн

**Почему:** В большинстве проектов достаточно обычных классов и композиции.

**Правильно:** Расширение built-ins — нишевый паттерн. Используйте только когда действительно нужно.


Важно запомнить

  • class MyArray extends Array — допустимый синтаксис
  • Symbol.species определяет тип возвращаемых методов
  • Методы возвращают экземпляр дочернего класса
  • Расширение built-ins — нишевый паттерн
  • Используйте композицию вместо наследования, когда возможно

Связь

Назад: Вы знаете классы (JI8) и наследование (JI9). Этот урок расширяет встроенные классы.

Вперёд: Следующий урок (JV14) — миксины — переиспользуемое поведение через композицию.

Материалы курса JS Advanced — PROlab Academy.