Преобразование типов

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

Ранее рассматривалась тема 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

С помощью оператора 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");
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850