Наконец-таки наследование здорового человека, через классы, а не через ломающие мозг прототипы! Теперь и в JavaScript! Скоро... Но пока в TypeScript. Реализация аналогична ООП в Java или C#.
Пример класса:
class Bird {
name: string;
constructor(name: string) {
this.name = name;
}
fly(hight: number) {
console.log(`${this.name} взлетает на ${hight} м.`)
}
}
let duck = new Bird('Дональд');
duck.fly(500);
Новый класс объявляется с помощью ключевого слова class
. У класса есть свой конструктор, поля (name
) и методы (fly
). Конструктор - функция, которая будет вызвана при создании нового экземпляра класса. Для обращения к полям класса используем this
. Для создания экземпляра класса используем оператор new
. Все просто.
Наследование
Воспользуемся классом Bird
из примера выше и унаследуемся от него в классах Duck
и Chicken
:
class Duck extends Bird {
constructor(name: string) { super(name); }
fly(hight = 10500) {
console.log('Кря...');
super.fly(hight);
}
}
class Chicken extends Bird {
constructor(name: string) { super(name); }
fly(hight = 3) {
console.log('Кудахтыж...');
super.fly(hight/2);
}
}
let donald = new Duck('Дональд');
let theSuperChicken: Bird = new Chicken('Cупер Кура');
donald.fly();
theSuperChicken.fly(50);
Наследование осуществляется с помощью ключевого слова extends
. Классы "Утки" и "Курицы" переопределяют метод fly
родительского класса "Птицы" в меру своих способностей. Для вызова конструктора или метода родительского класса используется super()
или super.fly()
соответственно.
Модификаторы доступа
public
Все члены класса в TypeScript по умолчанию являются публичными. Доступ к таким полям и методам класса возможен извне. Явно указать публичность члена класса можно с помощью public
:
class Bird {
public name: string;
public constructor(name: string) {
this.name = name;
}
public fly(hight: number) {
console.log(`${this.name} взлетает на ${hight} м.`)
}
}
private
Если поле или метод класса помечен как private
, то доступ к нему не возможен снаружи самого класса:
class Bird {
private name: string;
//...
}
let birdName = new Bird('Безымянный').name; // Ошибка: 'name' is private;
protected
Действие модификатора protected
схоже с private
, но члены класса с таким модификатором могут быть доступны для классов-наследников:
class Bird {
protected name: string; // защищенное поле 'name'
constructor(name: string) {
this.name = name;
}
fly(hight: number) {
console.log(`${this.name} взлетает на ${hight} м.`)
}
}
let birdName = new Bird('Безымянный').name; // Ошибка: 'name' is protected;
class Duck extends Bird {
constructor(name: string) { super(name); }
fly(hight = 10500) {
console.log(`${this.name} к взлету готов!`); // получаем доступ к полю 'name'
super.fly(hight);
}
}
let donald = new Duck('Дональд');
donald.fly();
У класса может быть protected
конструктор. Создать экземпляр этого класса не получится, но его можно унаследовать.
readonly
Свойства "только на чтение" должны быть инициализированы в конструкторе класса или при их объявлении:
class Bird {
readonly name: string;
readonly numOfWings = 2;
constructor(name: string) {
this.name = name;
}
}
let duck = new Bird('Скрудж Макдак'); // Надо вечерком перепройти Утиные истории...
duck.name = 'Дональд'; // Ошибка: 'name' is readonly
Свойства параметров конструктора
Последний пример можно немного сократить, объявив поле 'name' в параметрах конструктора:
class Bird {
readonly numOfWings = 2;
constructor(readonly name: string) { } // поле 'name' будет создано автоматически
}
В транспилированном JavaScript это будет выглядеть так:
var Bird = (function () {
function Bird(name) {
this.name = name;
this.numOfWings = 2;
}
return Bird;
}());
Можно не объявлять поле класса отдельной строкой, а можно делать это в параметрах конструктора, указав модификатор доступа (public
, private
, protected
) или readonly
. Соответствующее свойство класса будет создано автоматически.
static
Статические свойства класса (объявленные с использованием ключевого слова static
) не привязываются к конкретному экземпляру класса, а являются общими для всех. Доступ к таким полям или методам можно получить, используя название класса, где они объявлены (через точку).
class Bird {
constructor(private name: string) { }
static numOfWings = 2;
static setNumOfWings(num: number) {
Bird.numOfWings = num;
}
say () {
console.log(`${this.name}. Крыльев - ${Bird.numOfWings} шт.`);
}
}
let duck = new Bird('Утка');
duck.say(); // Утка. Крыльев - 2 шт.
Bird.setNumOfWings(4); // Меняем статическое свойство для всех птиц
let chicken = new Bird('Курица');
chicken.say(); // Курица. Крыльев - 4 шт.
В этом примере мы имеем статическое число крыльев static numOfWings
и статический метод для их изменения static setNumOfWings()
. Вызывая метод изменения числа крыльев для птиц, получаем изменение для всех экземпляров (уток и кур).
Геттеры и сеттеры
Геттеры (get
) и сеттеры (set
) в TypeScript - это специальные функции, которые позволяют организовать взаимодействие со свойствами объекта. В них можно реализовать некоторую логику получения и установки свойств.
let owner = true;
class Duck {
private _name: string;
get name(): string {
return this._name;
}
set name(name: string) {
if (owner) {
this._name = name;
}
else {
console.log('Ошибка: Только хозяин может давать имя!');
}
}
}
let duck = new Duck();
duck.name = 'Черный плащ';
if (duck.name) {
console.log(duck.name);
}
В примере выше мы организовали доступ к полю name
таким образом, что получить имя может любой, а установить - только хозяин.
Абстрактные классы
Что-то среднее между интерфейсами и обычными классами. Создать экземпляр абстрактного класса нельзя. Часть реализации может присутствовать. С помощью ключевого слова abstract
обозначаются как сами абстрактные классы, так и их методы, реализация которых в самом классе отсутствует, а должна быть написана в классах-наследниках.
abstract class Bird {
abstract say(): string;
fly(): void {
console.log('Лечу...');
}
}
Реализация метода say
должна быть описана для каждого класса, который будет наследовать класс Bird
.