Наследование

Последнее обновление: 18.11.2023

Одни классы могут наследоваться от других. Наследование позволяет сократить объем кода в классах-наследниках. Например, возьмем следующие классы:

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.

Производный класс также может определить свой конструктор. Если производный класс определяет конструктор, то в нем должен быть вызван конструктор базового класса. Для обращения производном классе к функциональности базового класса, в том числе для обращения к конструктору базового класса, применяется ключевое слово 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.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850