Классы
Немного о классах
Долгое время в JavaScript для создания однотипных объектов использовалась функция-конструктор. Функция-конструктор вызывается при помощи оператора new
. Эта функция заполняет полями и методами какой-то объект (обычно this
) и возвращает его (явно или не явно).
В ES6 появился синтаксис, который позволяет объявлять классы. По сути класс - это функция-конструктор каких-то объектов с упрощенным синтаксисом наследования, объявления методов и еще некоторыми особенностями. Само понятие класса лучше отражает объектно-ориентированный подход разработки, чем использование функций-конструкторов и прототипов.
Когда объявляется класс, в память кладется некоторый объект, который умеет создавать новые объекты при помощи оператора 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?