В первой статье мы говорили об абстракции, поэтому давайте рассмотрим следующий и очень важный принцип, особенно для JavaScript.
Наследование в JavaScript ES2018 имеет некоторые специфические отличия, если сравнивать с другими языками ООП, но синтаксис похож.
Наследование - это подход к разделению общих функций в коллекции классов. Это дает возможность избежать дублирования кода в классе, которому требуются те же данные и функции, которые уже есть в другом классе. В то же время это позволяет нам переопределить или расширить функциональность, которая должна иметь другое поведение.

На этой диаграмме ClassB и ClassD наследуют функциональность от ClassA. Это означает, что экземпляры этих классов, например ObjectB, будут иметь тот же список свойств и методов, что и ObjectA, являющийся экземпляром ClassA. И все же ObjectB имеет дополнительные свойства и методы, описанные в ClassB.

ClassA называется суперклассом или родительским классом для ClassB и ClassD, которые называются дочерними классами.

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

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

Наследование ClassC, ClassB и ClassA вызывает многоуровневый. Но наследование ClassB, ClassD из ClassA иерархического.

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

Давайте посмотрим на цепочку прототипов JavaScript объектов, отображаемых на диаграмме.

ObjectA был создан из ClassA. Внутреннее свойство объекта [[Prototype]] - это ссылка на прототип общедоступного свойства ClassA, который является самим объектом с собственным внутренним свойством [[Prototype]] , который является ссылкой на общедоступный прототип объекта, внутреннее свойство которого [[Prototype]] ссылается на null.

Почти все объекты в JavaScript имеют Object.prototype в верхней части цепочки прототипов.

Поскольку ObjectB является экземпляром ClassB, его внутренний [[Prototype]] ссылается на общедоступное свойство прототипа ClassB, которое является объектом и его внутренний [[Prototype]] ссылается на общедоступное свойство прототипа ClassA, поскольку ClassB расширяет ClassA, затем цепочку прототипов происходит так же, как в ClassA.prototype.

Точно так же ObjectC имеет ClassC.prototype как внутренний [[Prototype]], а следующая часть цепочки такая же, как у ObjectB.
Это выглядит сбивающим с толку и разработчики, которые не пытаются понять это, всегда жалуются, потому что это большая слабость. Но если вы изучите ее и сможете широко использовать, вы поймете, что модель наследования прототипа более мощная, чем классическая модель, поскольку классическую модель легко построить поверх модели прототипа.

Рассмотрим абстрактную реализацию цепочки прототипов, отображаемой на диаграмме.

В JavaScript ключевое слово extends используется для наследования родительского класса в объявлении класса class ClassB extends ClassA {.

Ключевое слово super используется для выполнения родительского конструктора (super();) и доступа к реализации родительских методов (super.methodB();).

Метод Object.getPrototypeOf (obj) возвращает прототип (т. Е. Значение внутреннего свойства [[Prototype]]) указанного объекта.

Object.prototype.constructor - это ссылка на функцию конструктора Object, которая использовалась для создания объекта-экземпляра.

Оператор instanceof проверяет, появляется ли свойство прототипа конструктора где-нибудь в цепочке прототипов объекта.

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

ClassA добавляет свойство propA к каждому новому его экземпляру внутри конструктора. В классе определен простой метод methodA.

ClassB расширяет ClassA, а внутри конструктора мы добавляем еще одно свойство propBto ClassB экземпляров. Каждый раз, когда мы определяем конструктор дочернего класса, мы должны вызывать функцию super перед обновлением this. Функция super применяет свойства родительского класса к this, созданному в дочернем классе, а не в прототипе. Ключевое слово super дает возможность повторно использовать методы родительского класса. Даже если метод переопределен в дочернем классе, super по-прежнему вызывает метод, реализованный в родительском классе. Метод methodA переопределяется в ClassB, поэтому this.methodA внутри ClassB является ссылкой на новую реализацию, сделанную в этом классе. Но если мы будем использовать super.methodA внутри ClassB, это будет относиться к реализации, сделанной в ClassA, несмотря на то, что она переопределена.

ClassC расширяет ClassB, поэтому его экземпляр должен иметь 3 свойства, а его methodC должен возвращать накопленное значение (CBNEW B).

В большинстве популярных браузеров реализовано свойство __proto__ объекта Object, которое эквивалентно внутреннему [[Prototype]], но нестандартно и использовать не рекомендуется. В любом случае, вы можете проверить это в консоли браузера, если хотите исследовать его.

Если мы пытаемся получить доступ к какому-либо свойству по ключу объекта, JavaScript ищет его ключ в собственных свойствах объекта. Если он не находит его там, то проходит по цепочке прототипов. Если он там находит, он возвращает значение, если нет, возвращает undefined.

Если мы хотим переопределить метод внутри дочернего класса, он просто добавляется к прототипу дочернего класса, но реализация метода родительского класса все еще находится где-то в цепочке прототипов, но не выполняется, поскольку JavaScript может найти метод на верхний уровень.

Если мы хотим переопределить метод внутри дочернего класса, он просто добавляется в прототип дочернего класса. Но реализация метода родительского класса все еще живет где-то (в Class.prototype, от которого мы наследуем) в цепочке прототипов, но не выполняется, поскольку JavaScript может найти метод на более высоком уровне (дочерний класс).

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