Классы
Немного о классах
Долгое время в JavaScript для создания однотипных объектов использовалась функция-конструктор. Функция-конструктор вызывается при помощи оператора new. Эта функция заполняет полями и методами какой-то объект (обычно this) и возвращает его (явно или не явно).
function Cat(name) {
this.name = name;
this.say = function() {
console.log(this.name, "say:", "meow");
}
}
const cat1 = new Cat("Tom");
const cat2 = new Cat("Lalaka");
cat1.say();
cat2.say();В ES6 появился синтаксис, который позволяет объявлять классы. По сути класс - это функция-конструктор каких-то объектов с упрощенным синтаксисом наследования, объявления методов и еще некоторыми особенностями. Само понятие класса лучше отражает объектно-ориентированный подход разработки, чем использование функций-конструкторов и прототипов.
class Cat {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name, "say:", "meow");
}
}
const cat1 = new Cat("Tom");
const cat2 = new Cat("Lalaka");
cat1.say();
cat2.say();Когда объявляется класс, в память кладется некоторый объект, который умеет создавать новые объекты при помощи оператора new. Объект класса - это функция конструктор, в котором хранится логика по заполнению новых других объектов. Объекты, которые были созданы при помощи какого-то класса называются экзэмплярами или инстансами (instance) этого класса.
Важно научится четко различать, что такое "объект класса" и "инстанс класса". Выше сказано, что объект класса - это объект в памяти (это функция). Это означает, что с объектом класса можно работать как с обычным объектам: добавлять поля, присваивать в качестве значения переменной, передавать в параметры функции (и в параметры конструктора другого класса).
Инстансы класса - это самостоятельные объекты, которые были сконструированы этого класса. Они не зависят друг от друга (если это не сделано специально) и с классом их связывает только то, что он являлся их создателем.
Объявление классов в TypeScript имеет ряд особенностей по сравнению с объявлением классов в JavaScript.
Поля классов
Класс может иметь поля. Для того, чтобы объявить поле, нужно указать его название и тип.
Поля могут иметь модификатор доступа. В TypeScript существует 3 модификатора доступа:
public- поле является публичным. Читать и изменять значение этого поля можно в любом местеprivate- поле является приватным. Читать и изменять значение этого поля можно только из методов самого классаprotected- поле является защищенным. Чистать и изменять значение этого поля можно из методов самого класса и его наследников
Модификаторы доступа полей нужны для того, чтобы правильно инкапсулировать логику внутри класса. private и protected поля хранят внутренне состояние класса. Это позволяет классу гарантировать, что значения в этих полях будут соответствовать внутренней логике класса (или потомка). Важно, что защита полей присутствует только на этапе компиляции. В JavaScript нет (пока) модификаторов доступа полей класса, поэтому в runtime значения приватных полей может быть изменено откуда угодно.
Если у поля не задан никакой модификатор доступа, то это поле public по умолчанию.
Для того, чтобы поле было доступно только на чтение, в TypeScript существует модификатор поля readonly. Он может применятся в месте с модификатором доступа. readonly поле должно быть инициализировано либо во время объявления, либо в конструкторе (об этом позже).
Поле класса может быть статичным. Статичное поле - это поле, значение которого хранится не в каждом конкретном инстансе класса (у каждого свое), а в самом объекте класса. Статичное поле помечается модификатором static. К статичному полю можно применять модификаторы доступа и модификатор readonly.
Важно отметить, что к статичному полю через инстанс класса доступа нет.
Из-за того, что статичные поля присваиваются только объекту самого класса, а не статичные - только инстансу класса, в TypeScript можно объявить у одного и того же класс статичное и не статичное поля с одинаковым именем:
Поля класса могут быть необязательными. Для этого после названия параметра нужно указать символ ? (как у объектов).
Конструктор
Выше уже было показано, как объявляется конструктор класса. Конструктор класса - это функция, которая вызывается для создания нового инстанса класса. Конструктор объявляется при помощи ключевого слова constructor. В конструкторе можно изменять значения полей класса (как публичных, так и приватных).
Ключевое слово this ссылается на тот инстанс класса, который сейчас конструируется. Этот же инстанс класса вернется в результате команды new A(). Конструктор класса - это функция. Она может принимать параметры. Параметры конструктора типизируются также, как типизируются параметры обычной функции.
Выше также было указано, что поля класса можно инициализировать во время объявления (in-place).
При создании инстанса класса, in-place поля инициализируются до вызова конструктора. К их значениям можно получить доступ из конструктора. Также в конструкторе можно изменить in-place значение поля.
Для удобства работы в TypeScript есть возможность объявлять поля сразу в конструкторе класса. Если параметру конструктора указать модификатор доступа, то у класс появится поле с именем и значением параметра и указанным модификатором доступа:
Методы
Помимо полей, класс может иметь методы. Метод класс - это функция, которая существует в рамках инстанса класса (статичная в рамках объекта класса). Эта функция имеет доступ к внутреннему состоянию класса. Она, как и другие функции, может иметь или не иметь возвращаемое значение и параметры класса (объявлять generic-параметры).
Методы класса могут иметь модификаторы доступа. Они такие же, как и модификаторы доступа полей. public методы могут быть вызваны, как внутри класса, так и снаружи; private - только внутри класса; protected - внутри класса или внутри класса наследника.
Модификатор readonly к методам не применим. А вот модификатор static применим и работает похожим образом, как и для полей. Метод присваивается не инстансам класса, а самому объекту класса. Соответственно, статичный метод не имеет доступа к полям инстансов класса, но имеет доступ к статичным полям и другим статичным методам.
Generic-параметры
Классу можно указывать generic-параметры. Их нужно указывать в <> сразу после имени класса. Generic-и выводятся по тем же принципам, что и при вызовах функций.
Методы класса могут объявлять собственные generic-и и использовать generic-и объявленные для класса.
Наследование
В TypeScript поддерживается возможность наследования классов. Наследование - одна из ключевых механик объектно-ориентированного программирования. При помощи наследования можно передать (унаследовать) поведение одного класса другому классу.
В примере выше класс Figure содержит метод print. Класс Circle наследуется (расширяет) класс Figure. В этом случаи Circle является наследником класса Figure, а класс Figure - базовом классом (или родительским классом, или суперклассом (superclass)) для Circle. Все поведение, которое определяется в классе Figure (в нашем случаи это метод print) передается классу Circle. При этом сам класс Circle объявляет свое собственное специфичное поведение (поле radius) в дополнение к унаследованному. Поэтому инстанс класса Circle имеет и поле radius и метод print.
Если у базового класса объявлен конструктор с обязательными параметрами, то его наследник в своем конструкторе должен вызывать конструктор базового класса. Иначе будет ошибка компиляции.
Ранее были рассмотрены модификаторы доступа полей и методов. Для того, чтобы сделать какое-то поле или метод доступным только внутри класса или внутри наследников этого класса, используется модификатор protected. private поля и методы базового класса доступны только внутри самого базового класса и наследникам не передаются.
Так как наследник расширяет базовый класс - он является надмножеством над базовым классом. С точки зрения типов это означает, что инстансы наследников базового класса могут передаваться туда, где требуется значение с типом базового класса.
В коде выше переменная q2 имеет тип Rect, но ей присваивается значение q1: Square. Это возможно, потому что тип Square имеет все поля типа Rect. Однако, не смотря на то, что в q2 лежит инстанс класса Square, поля и методы этого типа использовать нельзя, потому что тип переменной q2: Rect и TypeScript в общем случаи не может гарантировать, что в значении этой переменной лежит Square.
Наследоваться сразу от двух классов нельзя. TypeScript (как JavaScript) не поддерживает множественное наследование. Можно наследовать один класс от второго, а второй от третьего.
В TypeScript класс может быть абстрактным. Абстрактный класс - это класс, от которого можно наследовать другие классы, но нельзя создавать инстансы этого класса. Абстрактный класс помечается ключевым словом abstract.
Абстрактные классы могут иметь абстрактные методы. Абстрактные методы - это методы, у которых объявлена сигнатура, но не объявлено тело метода. Абстрактный метод помечается модификатором abstract. Наследник абстрактного класса обязан объявить у себя метод с такой же сигнатурой и определить реализацию этого метода. Если наследник не реализует абстрактные методы - будет ошибка компиляции.
Если наследником класса с абстрактными методами тоже является абстрактный класс, то абстрактные методы реализовывать не обязательно.
Абстрактные методы стоит воспринимать как интерфейс. Они гарантируют, что независимо от реализации абстрактного класса, они будут реализованы и иметь фиксированную сигнатуру.
Класс, как интерфейс
Инстанс класса - это объект. Как и любой другой объект в TypeScript, он имеет тип. Интерфейсом взаимодействия с инстансом класса из внешнего мира являются его публичные поля и методы. Поэтому, если тип объекта требует наличие каких-либо полей или методов у объекта, и тип класса имеет все эти поля и методы, то инстансы этого класса совместимы с типом объекта.
Тип класса сам может выступать в роли интерфейса.
В коде выше переменная a типизирована, как A. При этом A - это класс. Конструкция a: A не требует присвоения только инстансов класса A. Это конструкция требует любое совместимое с интерфейсом класса значение.
Для более строгой типизации TypeScript поддерживает реализацию интерфейса классом. Для этого используется ключевое слово implements.
Если указано, что класс реализует какой-то интерфейс, но в классе (или его базовом классе) не объявлены какие-то поля или методы, указанные в интерфейсе, то TypeScript выдаст ошибку компиляцию
Класс может реализовывать сразу несколько интерфейсов - их нужно перечислять через запятую после ключевого слова implements. Класс может одновременно наследоваться от другого класса и реализовывать множество интерфейсов.
Как было сказано ранее, классу Y не обязательно реализовывать интерфейсы A и B, для того, чтобы типы его инстансов были совместимы с этими интерфейсами. Реализация интерфейсов во многом нужна для более строгой типизации и облегчения работы в IDE.
Объект класса, как тип
Ранее уже говорилось, что объект класса - это своего рода функция, которая конструирует другие объекты (инстансы класса). Так как это объект, соответственно у него есть какой-то тип.
Объект класса можно вызывать как функцию с определенными параметрами, которая вернет определенное значение. Но есть одна важная особенность. Эту функцию-конструктор можно вызывать только с ключевым словом new. Поэтому тип объекта класса можно описать при помощи callable со следующей модификацией:
Выше был объявлен тип, который описывает множество объектов, которые можно вызывать с оператором new и двумя параметрами с заданными типами, и в результате этого вызова вернется объект с заданным интерфейсом.
Например, можно объявить тип-помощник, который будет возвращать тип объекта класса.
Задания
Last updated
Was this helpful?