Что такое super() в JavaScript?

Spread the love

Оригинальная статья: Bailey JonesWhat is super() in JavaScript?

Как работает код JavaScript, который вызывает super()?. Обычно вы используете super() для вызова конструктора родителя. А код типа super.<methodName> для доступа к методам родителя. Эта статья предполагает хотя бы небольшое знакомство с понятиями конструкторов дочерних и родительских классов. Если для вас эти понятия не известны, вы можете начать со статьи Mozilla об Object-oriented JavaScript for beginners.

Super не уникален для Javascript – многие языки программирования, включая Java и Python, имеют ключевое слово super(), которое предоставляет ссылку на родительский класс. Но JavaScript, в отличие от Java и Python, не строится вокруг классической модели наследования классов. Вместо этого он использует прототипную модель наследования JavaScript, обеспечивая поведение, соответствующее наследованию классов. (Чем прототипное наследование отличается от классического?)

Давайте узнаем немного больше об этом и рассмотрим некоторые примеры кода.

Прежде всего, вот цитата из веб-документов Mozilla для классов:

Классы JavaScript, представленные в ECMAScript 2015, в основном являются синтаксическим сахаром над существующим наследованием на основе прототипов JavaScript. Синтаксис класса не вводит новую объектно-ориентированную модель наследования в JavaScript.

Что на самом деле означает эта цитата можно проиллюстрировать на примере простого дочернего и родительского класса:

class Fish {
  constructor(habitat, length) {
    this.habitat = habitat
    this.length = length
  }
  
  renderProperties(element) {
    element.innerHTML = JSON.stringify(this)
  }
}

class Trout extends Fish {
  constructor(habitat, length, variety) {
    super(habitat, length)
    this.variety = variety
  }
  
   renderPropertiesWithSuper(element) {
    element.className="green" 
    super.renderProperties(element);
  }
}

let grouper = new Fish("saltwater", "26in");
console.log(grouper);
grouper.renderProperties(document.getElementById("grouper"));

let rainbowTrout = new Trout("freshwater", "14in", "rainbow");
console.log(rainbowTrout);

// вызываем функцию родителя
rainbowTrout.renderProperties(document.getElementById("rainbowTrout"));

//вызываем функцию потомка
rainbowTrout.renderPropertiesWithSuper(document.getElementById("rainbowTroutParent"));

Ссылка на codepan

В моем примере есть два класса: рыба fish и форель trout. Все рыбы имеют информацию о среде обитания habitat и длине length, поэтому эти свойства относятся к классу fish. Форель обладает свойством 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, а конструктор troutvariety. Мне нужно вызвать super() в конструкторе trout, или я получу ошибку referenceError, когда попытаюсь установить 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 нужно только включить Object, о котором JavaScript уже знает.

Модель наследования прототипа для рыбы и форели и свойства, доступные в этом контексте для каждого из них. Начиная сверху, прототипная цепочка наследования здесь переходит Object → fish → trout.

Я вызвал super(habitat, length) в конструкторе trout (ссылаясь на класс fish), сделав все три свойства сразу доступными в контексте this для trout. На самом деле есть другой способ получить то же поведение из конструктора trout. Я должен вызвать super(), чтобы избежать ошибки ссылки, но мне не нужно вызывать ее «правильно» с параметрами, ожидаемыми конструктором fish. Это потому, что мне не нужно использовать super() для присвоения значений полям, которые создает fish – я просто должен убедиться, что эти поля существуют в контексте this 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. Это был особо полезный пример реализации – но он действительно иллюстрирует, насколько могут быть гибкими классы в JavaScript.

Вполне возможно реализовать этот пример без использования super() или ключевых слов class и extends, которые были так полезны в предыдущем примере кода. Вот что Мозилла имела в виду под «синтаксическим сахаром». Фактически, если бы я подключил свой предыдущий код к transpiler, такому как Babel, чтобы убедиться, что мои классы работают с более старыми версиями JavaScript, он бы сгенерировал что-то ближе к следующему коду. Код здесь в основном такой же, но без class, extends и super(). fish и trout определены как функции. И все методы так же заданы как функции, но через prototype. Также добавлен код в строках 15, 16 и 17, чтобы установить trout в качестве потомка fish и прописана проверка, что trout может быть передан с правильным контекстом this в его конструктор.

function Fish(habitat, length) {
  this.habitat = habitat;
  this.length = length;
}

Fish.prototype.renderProperties = function(element) {
  element.innerHTML = JSON.stringify(this)
};

function Trout(habitat, length, variety) {
  this._super.call(this, habitat, length);
  this.variety = variety;
}
// Указываем что Trout потомок Fish
Trout.prototype = Object.create(Fish.prototype);
Trout.prototype.constructor = Trout;
Trout.prototype._super = Fish;

Trout.prototype.renderPropertiesWithSuper = function(element) {
  element.className="green"; 
  this.renderProperties(element);
};

let grouper = new Fish("saltwater", "26in");
grouper.renderProperties(document.getElementById("grouper"));

var rainbowTrout = new Trout("freshwater", "14in", "rainbow");

//вызов функции родителя
rainbowTrout.renderProperties(document.getElementById("rainbowTrout"));

//вызов фукнции потомка
rainbowTrout.renderPropertiesWithSuper(document.getElementById("rainbowTroutParent"));

Пример на codepan

Заключение

Классы в JavaScript – это мощный способ наследовать функциональность. Компоненты класса в React, например, полагаются на них. Однако, если вы привыкли к объектно-ориентированному программированию на другом языке, который использует модель наследования классов, поведение JavaScript может иногда вызывать удивление. Изучение основ наследования прототипов может помочь понять, как работать с классами в JavaScript.

Была ли вам полезна эта статья?
[22 / 4.8]

Spread the love
Подписаться
Уведомление о
guest
6 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Влад
Влад
3 лет назад

Отличнейшая статья, автор все разложил по полочкам, отдельное спасибо за наглядный пример)

Анонимно
Анонимно
3 лет назад

Хорошая статья

Last edited 3 лет назад by Анонимно
Анонимно
Анонимно
2 лет назад

Название среды codepEn, а не codepAn)))

Пипирос
Пипирос
1 год назад

Спасибо, очень информативно!

Анонимно
Анонимно
1 год назад

не известны

Maya
Maya
1 год назад

spasibo. ochen yasnoye obyasneniye!