От автора: что происходит, когда вы видите некоторый JavaScript, который вызывает super()? В дочернем классе вы используете super() для вызова конструктора его родителя и super.<methodName> для доступа к методам его родителя. Эта статья предполагает хотя бы небольшое знакомство с понятиями конструкторов и дочерних и родительских классов.
Super не уникален для Javascript — многие языки программирования, включая Java и Python, содержат ключевое слово super(), которое обеспечивает ссылку на родительский класс. JavaScript, в отличие от Java и Python, не строится вокруг модели наследования классов. Вместо этого он расширяет прототипную модель наследования JavaScript, обеспечивая поведение, соответствующее наследованию классов.
Давайте узнаем немного больше об этом и посмотрим на некоторые примеры кода. Прежде всего, вот цитата из веб-документации Mozilla:
Классы JavaScript, представленные в ECMAScript 2015, в основном являются синтаксическим сахаром по сравнению с существующим наследованием на основе прототипов JavaScript. Синтаксис класса не вводит новую объектно-ориентированную модель наследования в JavaScript.
Пример простого дочернего и родительского классов поможет проиллюстрировать, что на самом деле означает эта цитата:
В моем примере есть два класса: fish и trout. Все fish содержат информацию о habitat и length, поэтому эти свойства относятся к классу fish. trout также обладает свойством variety, поэтому он расширяет возможности fish на основе двух других свойств. Вот конструкторы для fish и trout:
class fish { constructor(habitat, length) { this.habitat = habitat this.length = length } } class trout extends fish { constructor(habitat, length, variety) { super(habitat, length) this.variety = variety } }
Конструктор класса fish определяет habitat и length, а конструктор trout — variety. Я должен вызвать super() в конструкторе trout, или я получу ошибку ссылки, когда попытаюсь установить this.variety. Это потому, что в первой строке класса trout я сказал JavaScript, что trout — потомок fish, используя ключевое слово extends. Это означает, что контекст this trout включает свойства и методы, определенные в классе fish, плюс любые свойства и методы, которые trout определяет для себя. Вызов, super() по сути, позволяет JavaScript знать, что такое fish, так что он может создать контекст this для trout, который включает в себя все от fish, плюс все, что мы собираемся определить для trout. Классу fish не нужен super() потому что его «родитель» — это просто объект JavaScript. Fish уже находится на вершине цепочки наследования прототипов, поэтому вызов super() не требуется — контекст this fish должен включать только объект, о котором JavaScript уже знает.
Модель наследования прототипа для fish и trout и свойства, доступные в этом контексте для каждого из них. Начиная сверху, цепочка наследования прототипа здесь Object→ fish→ trout
Я вызвал конструктор trout super(habitat, length) (ссылаясь на класс fish), сделав все три свойства сразу доступными в контексте this для trout. На самом деле есть другой способ получить то же поведение из конструктора trout. Мне нужно вызывать super(), чтобы избежать ошибки ссылки, но мне не нужно вызывать его «правильно» с параметрами, ожидаемыми конструктором fish. Это потому, что мне не нужно использовать super(), чтобы назначить значения полям, которые создает fish — я просто должен убедиться, что эти поля существуют в контексте trout. Это важное различие между JavaScript и истинной моделью наследования классов, такой как Java, где следующий код может быть недопустимым в зависимости от того, как я реализовал класс fish:
class trout extends fish { constructor(habitat, length, variety) { super() this.habitat = habitat this.length = length this.variety = variety } }
Этот альтернативный конструктор trout затрудняет определение того, какие свойства принадлежат fish, а какие — trout, но это дает тот же результат, что и в предыдущем примере. Единственное отличие состоит в том, что здесь, вызывая super() без параметров, мы создаем свойства habitat и length для текущего контекста this, не назначая им ничего. Если бы я вызвал console.log(this) после третьей строки, он вывел бы {habitat: undefined, length: undefined}. Строки четыре и пять назначают значения.
Я также могу использовать super() вне конструктора trout, чтобы сослаться на методы родительского класса. Здесь я определил метод renderProperties, который будет отображать все свойства класса в элементе HTML, который я ему передаю. super() здесь полезно, потому что я хочу, чтобы класс trout реализовал похожий метод, который делает то же самое и немного больше — он присваивает элементу имя класса перед обновлением его HTML. Я могу повторно использовать логику из класса fish, вызывая super.renderProperties() внутри соответствующей функции класса.
class fish { renderProperties(element) { element.innerHTML = JSON.stringify(this) } } class trout extends fish { renderPropertiesWithSuper(element) { element.className="green" super.renderProperties(element); }
Имя, которое вы выбираете, важно. Я вызвал метод в классе trout renderPropertiesWithSuper(), потому что я все еще хочу иметь возможность вызова trout.renderProperties(), как это определено в классе fish. Если бы я только вызвал функцию в классе trout renderProperties, это было бы совершенно корректно; однако я больше не смог бы получить доступ к обеим функциям напрямую из экземпляра trout — вызов trout.renderProperties вызвал бы функцию, определенную в trout. Это не обязательно полезная реализация — это, возможно, лучший шаблон для функции, которая вызывает super, как этот, чтобы перезаписать имя родительской функции — но она действительно иллюстрирует, насколько гибкими могут быть классы в JavaScript.
Вполне возможно реализовать этот пример без использования ключевых слов super() или extends, которые были так полезны в предыдущем примере кода, это гораздо менее удобно. Вот что Mozilla имеет в виду под «синтаксическим сахаром». Фактически, если бы я подключил свой предыдущий код к транспилеру, такому как Babel, чтобы убедиться, что классы работают с более старыми версиями JavaScript, это сгенерирует что-то ближе к следующему коду. Код здесь в основном тот же, но вы заметите, что без extends и super() мне нужно определить fish и trout как функции и получить доступ к их прототипам напрямую. Я также должен выполнить дополнительную разводку в строках 15, 16 и 17, чтобы установить trout в качестве потомка fish и обеспечить, чтобы trout могла быть передана с правильным контекстом в ее конструкторе.
Классы в JavaScript — это мощный способ разделить функционал. На них, например, полагаются компоненты класса в React. Однако, если вы привыкли к объектно-ориентированному программированию на другом языке, который использует модель наследования классов, поведение JavaScript может иногда вызывать удивление. Изучение основ наследования прототипов может помочь вам понять, как работать с классами в JavaScript.
Автор: Bailey Jones
Источник: https://css-tricks.com
Редакция: Команда webformyself.