JavaScript позволяет нам многое. Иногда его синтаксис бывает слишком снисходительным.

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

Также мы рассмотрим создание методов динамически.

method () Метод

Мы можем захотеть добавить method метод, чтобы мы могли создавать методы более классным способом.

Например, мы можем написать:

const Person = function(name) {
  this.name = name;
}.
method('getName', function() {
  return this.name;
})

Мы объединяем несколько методов в цепочку и используем метод method для определения новых методов вместо их прямого вызова.

Тогда мы можем назвать это следующим образом:

const a = new Person('joe'); 
a.getName();

Мы можем реализовать метод method следующим образом:

Person.prototype.method = function(name, implementation) {
  this.prototype[name] = implementation;
  return this;
};

Затем мы можем установить экземпляр метода Person, установив имя свойства прототипа динамически и установив функцию implementation в качестве метода экземпляра.

Мы также должны проверить, действительно ли implementation является функцией.

Итак, мы пишем:

const Person = function(name) {
  this.name = name;
}.
Person.prototype.method = function(name, implementation) {
  if (typeof implementation === 'function') {
    this.prototype[name] = implementation;
  }
  return this;
};

Прототипические и современные паттерны наследования

JavaScript использует прототипный шаблон наследования.

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

Это может быть не сразу очевидно, поскольку оператор new используется для создания объектов из конструкторов.

Синтаксис класса делает этот факт еще менее очевидным, поскольку многие люди будут введены в заблуждение синтаксисом класса и будут считать, что JavaScript на самом деле является объектно-ориентированным языком на основе классов.

Например, в Java мы пишем:

Person joe = new Person();

В JavaScript мы пишем:

const joe = new Person();

В JavaScript мы можем создать конструктор, наследующий от другого инструктора, следующим образом:

class Parent {
  constructor(name) {
    this.name = name;
  }
}
class Child extends Parent {}

Мы используем ключевое слово extends для создания дочернего класса, который наследуется от родительского класса.

Теперь мы можем получить доступ к переменной экземпляра name Parent через прототип Child, который является экземпляром Parent.

Теперь это стандартный способ создания дочернего конструктора из родительского конструктора.

Шаблон по умолчанию

В качестве альтернативы мы можем установить прототип дочернего класса в родительский класс.

Например, мы можем написать:

function Parent() {
  this.name = 'joe';
}
function Child() {}
const inherit = (C, P) => {
  C.prototype = new P();
}
inherit(Child, Parent);

Выше мы создали inherit метод, чтобы назначить прототип родительского P прототипу дочернего C.

Затем мы можем создать новый экземпляр Child и получить свойство name из прототипа экземпляра Child следующим образом:

console.log((new Child()).name);

Если мы запустим это, мы будем регистрировать 'joe', как это значение мы в конструкторе Parent.

Прототип экземпляра Child устанавливается как значение его свойства __proto__.

Проблема в том, что мы наследуем как свойства, добавленные в дочерний конструктор, так и унаследованные свойства прототипа.

Возможно, нам не нужны эти свойства, поэтому мы можем не захотеть использовать этот шаблон.

Антипаттерн - Аренда конструктора

Мы также можем использовать метод apply для вызова родительского конструктора.

Например, мы можем написать:

function Child(...args) {
  Parent.apply(this, args);
}

В приведенном выше коде у нас есть конструктор Child.

Он принимает аргумент args, и внутри него мы вызываем apply, где this является экземпляром Child.

args - это массив, аргументы которого мы передаем в конструктор.

В модели наследования выше у нас нет реальной связи между родителем и потомком.

Мы только что вызвали родительский конструктор из родительского элемента.

Если мы создадим экземпляр Child, мы не увидим ничего в свойстве __proto__ экземпляра Child, ссылающегося на Parent.

Следовательно, на самом деле он не выполняет никакого наследования.

Заключение

Стандартный способ наследования сегодня - использовать ключевое слово extends.

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

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