Как расширять методы класса в JavaScript
Что проверяют
Понимаете ли вы, как классы в JavaScript устроены поверх прототипов и как дополнить метод родителя, а не заменить его. Следом часто спрашивают про стрелочные функции в полях класса и потерю this.
Как отвечать
Базовый механизм: class Child extends Parent, переопределённый метод вызывает родительский через super.method(...args) и добавляет своё до или после. Методы класса лежат в прототипе, поэтому super.method ищет метод на прототипе родителя. В конструкторе наследника super() вызывается до первого обращения к this, иначе будет ReferenceError.
Способы расширить поведение:
- наследование с
super: подходит, когда наследник действительно разновидность родителя. Работает и для статических методов, и для встроенных классов вродеErrorилиHTMLElementв веб-компонентах; - миксины-функции:
const Serializable = (Base) => class extends Base { toJSON() {...} }, затемclass User extends Serializable(Model). Так добавляют поведение нескольким несвязанным классам; - композиция: объект держит помощника и делегирует ему вызов. Во фронтенде это обычно лучший вариант, React давно пришёл к хукам вместо наследования компонентов;
- обёртка метода: сохранить исходный метод и заменить его функцией, которая вызывает оригинал через
apply. На этом построены декораторы, стандартные декораторы TypeScript поддерживает с версии 5.0.
Где спотыкаются
Объявляют метод родителя стрелочной функцией в поле класса: handle = () => {}. Такое поле создаётся на экземпляре в конструкторе родителя, перекрывает одноимённый метод наследника на прототипе, и через super.handle его не вызвать.
Передают метод как колбэк, например в addEventListener, и теряют this. Нужен bind в конструкторе или стрелочная обёртка в месте вызова.
Расширяют встроенные прототипы вроде Array.prototype. Это конфликтует с будущими методами стандарта: как раз из-за старых библиотек, которые так делали, методы flatten и contains пришлось переименовать в flat и includes.