Ранее рассматривалась тема type assertion, где говорилось про преобразования типов. Те же способы преобразаний мы можем применять и при работе с классами и интерфейсами.
Так, рассмотрим следующую иерархию типов:
class Person { name: string; constructor(userName: string) { this.name = userName; } } class Employee extends Person { company: string; constructor(userName: string, company: string) { super(userName); this.company = company; } }
Здесь класс Employee унаследован от класса Person. Поскольку объекты Employee в то же время являются и объектами Person, то при определении объектов мы можем написать так:
let tom : Person = new Employee("Tom", "Microsoft");
Соответственно везде, где в функцию в качестве параметра передается объект Person или возвращается из функции объект Person, мы вместо объекта Person можем передавать объект Employee:
class Person { name: string; constructor(userName: string) { this.name = userName; } } class Employee extends Person { company: string; constructor(userName: string, company: string) { super(userName); this.company = company; } } function printPerson(user: Person): void{ console.log(`Person ${user.name}`); } function personFactory(userName: string): Person { return new Employee(userName, "не установлено"); } let tom : Person = new Employee("Tom", "Microsoft"); printPerson(tom); let bob = personFactory("Bob"); printPerson(bob);
Здесь продемонстрированы восходящиие преобразования, то есть преобразования от более конкретного типа к более общему - от призводного типа Employee к базовому типу Person. Они производятся неявно, и нам не надо писать какой-то дополнительный код.
Но есть и другой тип преобразований - нисходящие или от более общего типа к более конкретному. Например:
class Person { name: string; constructor(userName: string) { this.name = userName; } } class Employee extends Person { company: string; constructor(userName: string, company: string) { super(userName); this.company = company; } } let tom : Person = new Employee("Tom", "Microsoft"); console.log(tom.company); // ошибка - в классе Person нет свойства company
Здесь переменная tom имеет тип Person, однако в реальности эта переменная указывает на объект типа Employee, так как для ее инициализации мы использовали
конструктор типа Employee, который устанавливает свойство company. Однако попытка вывести значение свойства company у объекта tom
завершится ошибкой,
так как tom
- это все таки переменная типа Person, в котором нет свойства company
.
Чтобы решить эту ситуацию, нам надо явно преобразовать объект tom
к типу Employee
:
let tom : Person = new Employee("Tom", "Microsoft"); let tomEmployee: Employee = <Employee>tom; // преобразование к типу Employee console.log(tomEmployee.company); // или так console.log((<Employee>tom).company);
Выражение <Тип> переменная
позволяет преобразовать переменную к типу, который идет в угловых скобках.
Другой способ осуществить явное преобразование типов представляет применение оператора as:
let tom : Person = new Employee("Tom", "Microsoft"); let tomEmployee: Employee = tom as Employee; // преобразование к типу Employee console.log(tomEmployee.company); // или так console.log((tom as Employee).company);
Все сказанное в отношении преобразования классов будет справедливо и для преобразования интерфейсов. В то же время есть некоторые особенности. Пусть у нас будет интерфейс IPerson, никак не связанный с классами Person и Employee и ими не реализуемый:
interface IPerson { name: string; } class Person { name: string; constructor(userName: string) { this.name = userName; } } class Employee extends Person { company: string; constructor(userName: string, company: string) { super(userName); this.company = company; } } function printPerson(user: IPerson): void { console.log(`IPerson ${user.name}`); }
Функция printPerson в качестве параметра принимает объект интерфейса IPerson:
let tom: Person = new Employee("Tom", "Microsoft"); printPerson(tom); printPerson({ name: "Sam" }); //printPerson({ name: "Bob", company:"Microsoft" }); // ошибка
Ни класс Person, ни класс Employee не применяют интерфейс IPerson, однако мы можем их использовать, так как они имеют все те же свойства и методы, что интерфейс IPerson (в данном случае только свойство name).
Объект { name: "Sam" }
также является объектом интерфейса, так как он имеет свойство name
. В то же время при передаче объекта
{ name: "Bob", company:"Microsoft" }
мы получим ошибку, так как он уже расширяет возможности IPerson, добавляя свойство company
и напрямую интерфейсу IPerson не
соответствует. Но даже в этом случае мы его можем вполне использовать, применив преобразование типов:
printPerson({ name: "Bob", company:"Microsoft" } as IPerson);
С помощью оператора instanceOf можно проверить, принадлежит ли объект определенному классу:
let tom = new Employee("Tom", "Microsoft"); if (tom instanceof Person) { console.log("Tom is a Person"); } else { console.log("Tom is not a Person"); }