Наследование и полиморфизм в ECMAScript
View more Tutorials:
Перед изучением "Наследование в ECMAScript", удостоверьтесь, что у вас уже есть представление о "Классе и объекте", если нет, давайте изучим:
ECMAScript позволяет вам создать расширенный класс из одного или многих других классов. Этот класс называется производный класс (derived class) или просто подкласс.
Подкласс унаследует property, методы и другие члены от родительского класса. Он так же может переопределить(override) методы из родительского класса. В ECMAScript каждый класс имеет только 1 единственный constructor. Если класс сам не определяет свой собственный constructor, он унаследует constructor родительского класс. Наоборот, если подкласс определяет свой constructor, то не унаследует constructor родительского класса.
В отличие от Java, CSharp и некоторых других языков, ECMAScript допускает множественное наследование. Класс может быть расширен из одного или нескольких родительских классов.
Нам нужно несколько классов для примера.
- Animal (Животное): Класс имитирует животное.
- Duck (Утка): Класс имитирует утку, это подкласс Животного.
- Cat (Кот): Класс имитирует кота, это подкласс Животного.
- Mouse (Мышь): Класс имитирует мышь, это подкласс Животного.

В ECMAScript, конструктор (constructor) используется для создания объекта и прикрепляет значение property (свойства). Конструктор подклассов всегда называется конструктором родителя, чтобы инициализировать значение для property родительского класса, затем он прикрепляет значение этим атрибутам.
Например:
Cat является подклассом, унаследованным от класса Animal, он тоже имеет property этого класса.
inherit-example1.js
class Animal { constructor(name) { this.name = name; } showInfo() { console.log("I'm " + this.name); } move() { console.log("Moving.."); } } class Cat extends Animal { constructor(name, age, height) { super(name); // Cat's properties: this.age = age; this.height = height; } // Переопределить (override) метод родительского класса. showInfo() { console.log("My name is " + this.name); } // Other method... sleep() { console.log("Sleeping.."); } } // ------------- TEST -------------------- let tom = new Cat("Tom", 3, 20); console.log("Call move() method"); tom.move(); console.log("\n"); console.log("Call showInfo() method"); tom.showInfo();

Что случится, когда вы создаёте объект конструктором (constructor)? Как он вызовет коструктор родительского класса? Смотрите иллюстрацию ниже:

С иллюстрации выше вы видите, что конструктор (constructor) родителькского класса вызывается в конструкторе подкласса, она присвоит значения properties родительского класса, затем и properties подкласса.
По умолчанию, подкласс унаследует методы из родительского класса, но подкласс может переопределять (override) методы родительского класса.
inheritance-example2.js
class Animal { constructor(name) { this.name = name; } showInfo() { console.log("I'm " + this.name); } move() { console.log("Moving.."); } } class Mouse extends Animal { constructor(name, age, height) { super(name); this.age = age; this.height = height; } // Переопределить (override) метод родительского класса. showInfo() { // Вызвать метод showInfo() родительского класса. super.showInfo(); console.log ("Age: " + this.age); console.log ("Height: " + this.height); } // Переопределить (override) метод родительского класса. move() { console.log("Mouse Moving.."); } } // ------------- TEST -------------------- let jerry = new Mouse("Jerry", 3, 5); console.log("Call move() method"); jerry.move(); console.log("\n"); console.log("Call showInfo() method"); jerry.showInfo();

Понятие абстракного метода (abstract method) или абстракного класса (abstract class) имеется в таких языках как Java, C#. Но не полностью определяется в ECMAScript. Все же у нас есть способ определить его.
Класс, называется абстрактным (abstract), определяет абстрактные методы и подкласс должен переопределять (override) эти методы, если хочет их использовать. Абстрактные методы всегда вызывают исключение "Not Implemented Error".
abstract-example.js
// An abstract class. class AbstractDocument { constructor(name) { this.name = name } // A method can not be used, because it always throws an error. show() { // Not Implemented Error throw "Subclass must implement abstract method"; } } class PDF extends AbstractDocument { // Override method of parent class show() { console.log("Show PDF document:", this.name); } } class Word extends AbstractDocument { show() { console.log("Show Word document:", this.name) } } // -------------------- TEST ------------------------- let doc1 = new PDF("Python tutorial"); let doc2 = new Word("Java IO Tutorial"); let doc3 = new PDF("ECMAScript Date & Time Tutorial"); let documents = [doc1, doc2, doc3]; for (let i = 0; i < documents.length; i++) { documents[i].show(); } let doc4 = new AbstractDocument("An Abstract Document"); doc4.show(); // Throw Error!!!

В приведенном выше примере демонстрируется полиморфизм (Polymorphism) в ECMAScript. Объект Document (документ) может быть представлен в различных формах (PDF, Word, Excel, ...).
Другой пример иллюстрирует полиморфизм: когда я говорю об азиатском человеке, это довольно абстрактно, он может быть японцем, вьетнамцем или индийцем. Однако, у них у всеъ есть особенности азиатских людей.
instanceof
Oператор instanceof помогает вам проверить, является ли "что-то" объектом определенного класса или нет.
instanceof-example.js
class Person { } // A Child class of Person class AsianPerson extends Person { } class Triangle { } let person = new Person(); let asianPerson = new AsianPerson(); let triangle = new Triangle(); let isPerson = person instanceof Person; console.log( isPerson ); // true console.log( asianPerson instanceof Person ); // true console.log( person instanceof AsianPerson ); // false console.log( triangle instanceof Person ); // false console.log( triangle instanceof AsianPerson ); // false
typeof
Оператор typeof используется для проверки вида "чего-то", полученный результат будет название вида.
Type | Result |
---|---|
undefined | "undefined" |
null | "object" |
boolean | "boolean" |
number | "number" |
String | "string" |
Symbol (new in ECMAScript 6) | "symbol" |
Host object (provided by the JS environment) | Implementation-dependent |
Function object (implements [[Call]] in ECMA-262 terms) | "function" |
Any other object | "object" |
typeof-example.js
// A Class: class Person { } // An Object: let person = new Person(); console.log( typeof Person ); // function console.log( typeof person ); // object // null: console.log( typeof null ); // object // A Number: let intValue = 100; console.log( typeof intValue ); // number // NaN (Not a Number) console.log( typeof NaN); // number // A String let strValue = "Hello"; console.log( typeof strValue ); // string // A Boolean: let boolValue = true; console.log( typeof boolValue ); // boolean // undefined console.log( typeof undefined); // undefined // An Array let years = [1980, 2003, 2018]; console.log( typeof years ); // object // A Function let myFunc = function() { } console.log( typeof myFunc ); // function // A Symbol let mySymbol = Symbol(); console.log( typeof mySymbol ); // symbol
Is Sub Class?
Например у вас есть 2 класса A & B. Пример ниже поможет вам проверить является ли A подклассом у B или нет.
let isChild = A === B || A.prototype instanceof B;
issubclass-example.js
class Animal { } class Cat extends Animal { } class AsianCat extends Cat { } // ------------- TEST -------------------- console.log("AsianCat === Animal? " + (AsianCat === Animal)); // false let isSubClass1 = AsianCat === Animal || AsianCat.prototype instanceof Animal; console.log("AsianCat is child of Animal? " + isSubClass1); // true let isSubClass2 = AsianCat === Animal || Animal.prototype instanceof AsianCat; console.log("Animal is child of AsianCat? " + isSubClass2); // false

Языки Java, C# очень близки по виду данных. Поэтому когда вы вызываете функцию вам нужно передать правильный вид данных соответствующие параметрам. В ECMAScript при вызове функции вы можете передать параметр с любым видом данных, может быть риск, и программист должен сам управлять этими рисками.
Здесь я создаю два класса, English и French. У обоих классов есть метод greeting(). Оба создают разные приветствия. Создайте два соответствующих объекта из двух классов выше и вызовите действия этих двух объектов в одной функции (функция intro).
polymorphism-example.js
class English { greeting() { console.log("Hello"); } } class French { greeting() { console.log("Bonjour"); } } function intro(language) { language.greeting(); } // -------------- TEST ---------------------- let flora = new English(); let aalase = new French(); // Call function: intro(flora); intro(aalase); let someObject = {}; intro(someObject);// Error!!!
Запуск примера:

OK, изменить код выше, добавить нужные проверки чтобы избежать риски для вашего компьютера:
polymorphism-example2.js
class English { greeting() { console.log("Hello"); } } class French { greeting() { console.log("Bonjour"); } } function intro(language) { // Check type of 'language' object. if(language instanceof English || language instanceof French) { language.greeting(); } } // -------------- TEST ---------------------- let flora = new English(); let aalase = new French(); // Call function: intro(flora); intro(aalase); let someObject = {}; intro(someObject);
Применить архитектуру "Класс & Наследствие" помогает легче управлять ваше приложение и избежать ошибки.
polymorphism-example3.js
// A Base class class Language { } class English extends Language { greeting() { console.log("Hello"); } } class French extends Language { greeting() { console.log("Bonjour"); } } function intro(language) { // Check type of 'language' object. if(language instanceof Language) { language.greeting(); } } // -------------- TEST ---------------------- let flora = new English(); let aalase = new French(); // Call function: intro(flora); intro(aalase); let someObject = {}; intro(someObject);
Смотрите так же: