W3docs

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

Расширение встроенных классов в JavaScript: синтаксис, Symbol.species, кастомные ошибки и лучшие практики.

Встроенные классы, такие как Array, Map, Set и Error, под капотом являются обычными классами, поэтому их можно расширять с помощью extends точно так же, как любой собственный класс. Подкласс наследует все методы родителя и может добавлять новые методы, переопределять поведение или хранить дополнительное состояние. Это удобный способ создать специализированную коллекцию (массив, умеющий суммировать себя, или map со значениями по умолчанию) или тип ошибки для конкретной предметной области — не затрагивая глобальный прототип, что является более рискованной альтернативой, описанной в JavaScript: Native Prototypes.

В этой главе рассматриваются базовый синтаксис, то, как встроенные методы возвращают экземпляры вашего подкласса, как Symbol.species позволяет это контролировать, расширение Error для создания пользовательских типов ошибок, а также нюансы, которые стоит знать перед применением этой техники. Если вы ещё не знакомы с extends и super, сначала прочитайте JavaScript: Class Inheritance.

Синтаксис и базовый пример

Синтаксис идентичен расширению любого другого класса:

class CustomClass extends BuiltInClass {
  // New methods and properties to extend the built-in class
}

Если в подклассе определён конструктор, необходимо вызвать super() до обращения к this. Это позволяет родительскому классу правильно инициализировать своё внутреннее состояние и нативные структуры — для Array и Map именно внутреннее состояние обеспечивает их работу.

Расширим класс Array методом для суммирования всех элементов:

javascript— editable

Экземпляр ведёт себя как полноценный массив — индексирование, length, итерация и все методы Array работают — плюс добавленный метод sum().

⚠️ Примечание: При расширении Array учитывайте, что свойство length в JavaScript ведёт себя особым образом. В некоторых окружениях length может не синхронизироваться автоматически с фактическим размером массива при использовании определённых нативных методов. Тщательно тестируйте или рассмотрите композицию, если точное отслеживание length критично.

Встроенные методы возвращают экземпляры вашего подкласса

Именно это делает расширение Array по-настоящему мощным: методы вроде map, filter и slice, возвращающие новый массив, возвращают экземпляр вашего подкласса, а не обычный Array. Это значит, что новый массив по-прежнему содержит ваши пользовательские методы.

javascript— editable

Внутри движок определяет, какой конструктор использовать, через специальный статический геттер Symbol.species. По умолчанию Symbol.species возвращает сам подкласс, поэтому filter в примере выше вернул ExtendedArray.

Управление возвращаемым типом с помощью Symbol.species

Иногда нужно обратное: методы вроде map и filter должны возвращать обычные массивы, тогда как new ExtendedArray(...) по-прежнему даёт экземпляр подкласса. Переопределите Symbol.species, чтобы он указывал на базовый класс:

javascript— editable

Теперь arr является PowerArray с методом isEmpty(), но filter возвращает обычный Array, который больше не несёт этот метод. Используйте это, когда подкласс добавляет состояние в конструктор, которое производные массивы не должны наследовать. Symbol.species также учитывается в Map, Set, ArrayBuffer и Promise.

Расширение класса String

Класс String — ещё один фундаментальный встроенный объект, который можно расширять дополнительными вспомогательными методами для работы со строками.

Добавление функции реверса

javascript— editable

⚠️ Примечание: Расширение String в целом не рекомендуется из-за особенностей приведения примитивов к объектам в JavaScript, что может вызывать непредвиденное поведение нативных методов. Для работы со строками предпочтительнее использовать вспомогательные функции или композицию вместо наследования.

Настройка класса Map

Класс Map в JavaScript представляет коллекцию элементов с ключами и обеспечивает более продвинутый и гибкий способ хранения данных по сравнению с объектами. Расширение класса Map позволяет добавить более специализированное поведение.

Реализация значения по умолчанию

Расширение Map для возврата значения по умолчанию, если ключ не существует. Обратите внимание, как переопределённый get вызывает super.get(key) для обращения к реальному поиску в Map:

javascript— editable

Расширение класса Error

Распространённое и практичное применение этой возможности — создание пользовательских типов ошибок. Создание подкласса Error даёт именованную ошибку, которую можно проверять с помощью instanceof, при этом она остаётся настоящей ошибкой Error (с полями message, stack и совместимостью с try...catch).

javascript— editable

Установка this.name важна — она определяет, как выводится ошибка, и позволяет отличить ваш тип ошибки от обобщённого. Для более подробного рассмотрения, включая построение иерархии классов ошибок, см. JavaScript: Custom Errors, Extending Error.

Лучшие практики и рекомендации

Несмотря на то что расширение встроенных классов открывает широкие возможности, важно соблюдать лучшие практики для обеспечения поддерживаемости и совместимости кода.

  • Избегайте переопределения существующих методов: добавление новых методов при расширении встроенных классов, как правило, безопасно. Однако переопределение существующих методов может привести к непредсказуемому поведению и проблемам совместимости.
  • Используйте при конкретной необходимости: расширяйте встроенные классы только при наличии очевидной выгоды или необходимости. Избегайте излишних расширений, которые могут усложнить кодовую базу.
  • Предпочитайте композицию или вспомогательные функции: в современном JavaScript расширение встроенных классов зачастую излишне. Использование вспомогательных функций или композиции обычно даёт более чистые и предсказуемые результаты, не затрагивая внутренние механизмы нативных подклассов.
  • Документируйте расширения чётко: убедитесь, что любые расширения встроенных классов хорошо задокументированы в кодовой базе, чтобы не вводить в заблуждение других разработчиков.

Статические методы тоже наследуются

При расширении встроенного класса через extends подкласс также наследует статические методы родителя. Таким образом, ExtendedArray.from(...) и ExtendedArray.isArray(...) доступны, а статические фабричные методы вроде Array.from создают экземпляры подкласса. Это повторяет обычное наследование классов — см. JavaScript: Static Properties and Methods о том, как наследуются статические члены.

Заключение

Расширение встроенных классов — чистый способ добавить специализированное поведение к нативным объектам (суммирующий массив, map со значением по умолчанию, именованная ошибка) без изменения глобальных прототипов. Ключевая идея: производные встроенные методы по умолчанию возвращают экземпляры вашего подкласса, а Symbol.species — это инструмент для изменения этого поведения. Применяйте наследование, когда существует истинное отношение «является»; в остальных случаях предпочитайте обычные вспомогательные функции или композицию.

Практика

Практика
Какие утверждения верны относительно расширения встроенных классов в JavaScript?
Какие утверждения верны относительно расширения встроенных классов в JavaScript?
Was this page helpful?