Одни классы могут наследоваться от других. Наследование позволяет сократить объем кода в классах-наследниках. Например, возьмем следующие классы:
class Person{ name; age; print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee{ name; age; company; print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } work(){ console.log(`${this.name} works in ${this.company}`); } } const tom = new Person(); tom.name = "Tom"; tom.age= 34; const bob = new Employee(); bob.name = "Bob"; bob.age = 36; bob.company = "Google"; tom.print(); // Name: Tom Age: 34 bob.print(); // Name: Bob Age: 36 bob.work(); // Bob works in Google
Здесь определены два класса - Person, который представляет человека, и Employee, который представляет работника предприятия. Оба класса прекрасно работают, мы можем создавать их объекты, но мы также видим, что класс Employee повторяет функционал класса Person, так как работник также является человеком, для которого также можно определить свойства name и age и метод print.
Наследование позволяет одним классам автоматически получить функцонал других классов и тем самым сократить объем кода. Для наследования одного класса от другого применяется ключевое слово extends:
class Base{} class Derived extends Base{}
После названия класса-наследника ставится ключевое слово extends, после которого идет имя класса, от которого мы хотим унаследовать функционал.
Так, изменим классы Person и Employee, применив наследование:
class Person{ name; age; print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ company; work(){ console.log(`${this.name} works in ${this.company}`); } } const tom = new Person(); tom.name = "Tom"; tom.age= 34; const bob = new Employee(); bob.name = "Bob"; bob.age = 36; bob.company = "Google"; tom.print(); // Name: Tom Age: 34 bob.print(); // Name: Bob Age: 36 bob.work(); // Bob works in Google
Теперь класс Employee наследуется от класса Person. В этом отношении класс Person еще называется базовым или родительским классом, а Employee - производным классом или классом-наследником. Поскольку класс Employee наследует функционал от Person, то нам нет необходимости заново определять в нем свойства name, age и метод print. В итоге код класса Employee получился короче, а результат программы тот же.
Вместе со всем функционалом производный класс наследует и конструктор базового класса. Например, определим в базовом классе Person конструктор:
class Person{ constructor(name, age){ this.name = name; this.age = age; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ company; work(){ console.log(`${this.name} works in ${this.company}`); } } const tom = new Person("Tom", 34); tom.print(); // Name: Tom Age: 34 const sam = new Employee("Sam", 25); // унаследованный конструктор sam.print(); // Name: Sam Age: 25
В данном случае класс Person определяет конструктор с двумя параметрами. В этом случае класс Employee наследует его и использует для создания объекта Employee.
Производный класс также может определить свой конструктор. Если производный класс определяет конструктор, то в нем должен быть вызван конструктор базового класса. Для обращения производном классе к функциональности базового класса, в том числе для обращения к конструктору базового класса, применяется ключевое слово super
class Person{ constructor(name, age){ this.name = name; this.age = age; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } work(){ console.log(`${this.name} works in ${this.company}`); } } const tom = new Person("Tom", 34); tom.print(); // Name: Tom Age: 34 const sam = new Employee("Sam", 25, "Google"); sam.print(); // Name: Sam Age: 25 sam.work(); // Sam works in Google
Класс Employee определяет свой конструктор с тремя параметрами, первой строкой в котором идет обращение к конструктору базового класса Person:
super(name, age);
Поскольку конструктор класса Person имеет два параметра, соответственно в него передаются два значения. При этом конструктор базового класса должен вызываться до
обращения к свойствам текущего объекта через this
.
Производный класс, как и в случае с конструктором, может переопределять методы базового класса. Так, в примере выше метод print()
класса Person
выводит имя и возраст человека. Но что, если мы хотим, чтобы для работника метод print()
выводил также и компанию? В этом случае мы можем определить в классе Employee
свой метод print()
:
class Person{ constructor(name, age){ this.name = name; this.age = age; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); console.log(`Company: ${this.company}`); } } const sam = new Employee("Sam", 25, "Google"); sam.print(); // Name: Sam Age: 25 // Company: Google
Однако в коде выше мы видим, что первая строка метода print()
в классе Employee по сути повторяет код метода print()
из
класса Person. В данном случае это всего одна строка, но в другой ситуации повторяемый код мог бы больше. И чтобы не повторяться, мы опять же можем
просто обратиться к реализации метода print()
родительского класса Person через super:
class Person{ constructor(name, age){ this.name = name; this.age = age; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } print(){ super.print(); console.log(`Company: ${this.company}`); } } const sam = new Employee("Sam", 25, "Google"); sam.print(); // Name: Sam Age: 25 // Company: Google
То есть в данном случае вызов
super.print();
представляет вызов реализации метода из базового класса. Таким образом, с помощью this и super мы можем разграничить обращение к функциональности текущего класса или его базового класса.
При наследовании стоит учитывать, что производный класс может обращаться к любой функциональности базового класса, кроме приватных полей и методов. Например:
class Person{ #name; constructor(name, age){ this.#name = name; this.age = age; } print(){ console.log(`Name: ${this.#name} Age: ${this.age}`); } } class Employee extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } print(){ super.print(); console.log(`Company: ${this.company}`); } work(){ console.log(`${this.#name} works in ${this.company}`); // ! Ошибка - поле #name недоступно из Employee } }
В данном случае поле #name
в классе Person определено как приватное, поэтому достуно только внутри этого класса. Поытка обратиться
к этому полю в классе-наследнике Employee приведет к ошибке вне зависимости будет идти обращение через this.#name
или super.#name
.
При необходимости в базовом классе можно определить геттеры и сеттеры, которые обращаются к приватным полям. А в классе-наследники через эти геттеры и сеттеры обращаться к приватным полям базового класса.
Тот факт, что класс-наследник унаследован от некоторого базового класса говорит о том, что объект класса-наследника также является объектом базового класса. Объектом какого класса является объект, можно проверить с помощью оператора instanceof:
class Person{ constructor(name, age){ this.name = name; this.age = age; } print(){ console.log(`Name: ${this.name} Age: ${this.age}`); } } class Employee extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } print(){ super.print(); console.log(`Works in ${this.company}`); } } class Manager extends Person{ constructor(name, age, company){ super(name, age); this.company = company; } print(){ super.print(); console.log(`Manager in ${this.company}`); } } const sam = new Employee("Sam", 25, "Google"); console.log(sam instanceof Person); // true console.log(sam instanceof Employee); // true console.log(sam instanceof Manager); // false
Здесь константа sam представляет объект класса Employee, который унаследован от Person, соответственно выражения sam instanceof Person
и sam instanceof Employee
возвратят true
. А вот объектом класса Manager константа sam не является, поэтому выражение sam instanceof Manager
возвратит false.