Классы в TypeScript

Sergey Bakaev
5 min readSep 13, 2017

--

Введение

В традиционном JavaScript для построения повторно используемых компонентов применяются функции и наследование на прототипах. Это может показаться несколько сложным для программистов, которым ближе объектно-ориентированный подход, где классы могут наследовать функционал класса-родителя. Начиная с ECMAScript 6, JavaScript также позволяет использовать объектно-ориентированный подход, но потом все равно придется применять различные конвертеры из ECMAScript 6 в традиционный JavaScript, поскольку новые форматы языка не везде поддерживаются. TypeScript позволяет использовать эти преимущества разработки уже сейчас, а компилятор сам позаботится о том, чтобы превратить код в формат, который будут поддерживать все основные браузеры.

Простой пример класса

Простой пример класса на языке TypeScript

Синтаксис может показаться очень знакомым, если ранее вы использовали C# или Java. В данном примере мы определили класс Greeter. Он имеет три члена: свойство greeting, конструктор и метод greet.

Для доступа к членам класса внутри него самого нужно использовать ключевое слово this перед именем свойства или метода.

В последней строке примера мы создаем экземпляр класса Greeter, используя ключевое слово new. Далее указываем конструктор, определенный ранее в классе и передаем туда параметр message.

Наследование

Одним из основных шаблонов объектно-ориентированного программирования является наследование классов. С помощью него можно создавать новые классы на основе существующих.

Рассмотрим пример:

Ключевое слово extends используется для создания подкласса на основе другого класса, который еще называют базовым. В нашем примере Shape — базовый класс, а Circle и Rect являются его подклассами. Производные классы получают доступ к методам и свойствам базового класса.

В конструкторе производного класса обязательно должен быть вызван метод super(), который вызовет конструктор базового класса. В противном случае компилятор выдаст сообщение об ошибке.

Данный пример также демонстрирует, как осуществляется переопределение методов базового класса. Классы Circle и Rect переопределяют метод translate(point: Point), позволяя изменять поведение метода. Обратите внимание, что экземпляр класса Rect объявлен как let rect: Shape, т.е. имеет тип Shape, но при вызове translate() будет вызван метод, переопределенный в производном классе.

Результат работы программы

Для вызова метода базового класса, также как и для конструктора, используется ключевое слово super. При этом, в отличие от конструктора, оно не является обязательным. В этом случае можно сказать, что переопределенный метод полностью скрывает функционал базового метода.

Модификаторы доступа (public, protected, private)

public по умолчанию

В примере выше, мы имели доступ ко всем свойствам и методам классов. Если вы знакомы с другими языками объектно-ориентированного программирования, то это могло показаться вам немного странным. Ведь для этого вам пришлось бы использовать ключевое слово public, как, например, в языке C#. В языке TypeScript все свойства и методы являются публичными по умолчанию.

Однако ничего не мешает вам указывать публичность метода или свойства явно. Например, для класс Shape из примера выше мы могли бы переписать следующим образом:

Модификатор private

Если член класса помечен как private, то он будет доступен только в пределах самого класса. Например:

Язык TypeScript — это структурная система типов. Когда сравниваются два различных типа, то даже несмотря на то, что неизвестно откуда они взяты, если типы всех членов совместимы, то можно говорить о совместимости самих типов друг с другом.

Однако все обстоит несколько иначе с теми типами, которые имеют модификаторы доступа, такие как private и protected. Если один тип имеет private поле, то для того, чтобы два типа были совместимы, второй тип должен иметь такое же поле, объявленное точно там же, где и у первого. Проще пояснить на примере:

Даже несмотря на то, что класс OtherShape имеет точно такую же структуру, как и Shape, присвоение переменной shape экземпляра ExtendedShape вызовет ошибку компиляции.

Все сказанное выше относится и к модификатору protected.

Модификатор protected

Данный модификатор отличается от private тем, что определенные с помощью него члены класса, будут доступны в производных классах.

Рассмотрим пример:

В производном классе Circle у нас есть доступ к переменной center, однако вне класса обращение к ней вызовет ошибку компиляции.

Конструктор также может быть помечен модификатором protected. В этом случае класс не может быть инициализирован, но может быть унаследован. Например:

Модификатор readonly

Можно сделать свойства класса доступными только для чтения. Для этих целей предназначен модификатор readonly. Такие свойства могут быть инициализированы только в конструкторе:

readonly можно использовать совместно с private, public и protected. Например, если нужно сделать доступным только для чтения закрытое (private) свойство класса, то нужно написать private readonly prop: SomeType:

Свойства — параметры (конструктора)

В предыдущем примере мы определили свойство name, доступное только для чтения, а затем инициализировали его в конструкторе. Это достаточно распространенная практика. Однако, используя свойства-параметры, можно создать и инициализировать свойство в одном месте — параметре конструктора:

Данное определение класса будет полностью аналогично определению из примера выше.

Также можно использовать и другие модификаторы для создания свойств. Например, если вместо readonly использовать private, то будет создано закрытое свойство класса.

Методы доступа (Accessors)

TypeScript поддерживает геттеры и сеттеры для того, чтобы перехватить доступ к свойствам объектов. Это позволяет нам более тщательно контролировать доступ к свойствам объекта.

Рассмотрим пример использования геттеров и сеттеров. Для начала начнем с класса без них:

В данном примере пользователь может менять значения свойства name в любом месте и когда захочет.

Но что если нам нужно ограничить доступ к установке нового значения этого свойства. Скажем, его можно установить только если оно удовлетворяет определенным критериям, например, не может быть равно some name. Помимо этого мы хотим, чтобы при получении значения свойства (даже если значение не было установлено), возвращалась пустая строка вместо undefined. Для всего этого удобно использовать геттеры и сеттеры.

Для работы геттеров и сеттеров должен быть установлен ECMAScript5 или выше в качестве выходного формата компиляции в JavaScript.

Свойства, которые имеют только геттер автоматически интерпретируются как readonly. Это может быть полезным при генерации .d.ts файла, чтобы пользователи видели, что эти свойства доступны только для чтения и их значения нельзя изменять.

Статичные свойства

Статичные свойства — это свойства, значения которых одинаковы для всех экземпляров класса. Определяются такие свойства с помощью ключевого слова static. Доступ к таким свойствам осуществляется по шаблону <ClassName>.<PropertyName>.

Абстрактные классы

Абстрактные класс — это базовый класс, на основе которого могут быть созданы другие классы при помощи наследования. В отличие от обычных классов, экземпляр абстрактного создать нельзя. Для объявления абстрактного класса используется ключевое слово abstract. Тоже самое применимо и для методов и свойств.

Абстрактные методы не должны быть реализованы и должны быть переопределены в производном классе. Они похоже по синтаксису на определение методов в интерфейсе. И интерфейс, и абстрактный класс определяют сигнатуру метода без реализации, но абстрактные методы должны быть определены с помощью ключевого слова abstract и могут иметь модификаторы доступа.

Дополнительно

Функции конструктора

Рассмотрим пример:

Что же происходит в этом примере? Сначала мы определяем тип, который будут иметь экземпляры класса — SomeClass. Затем мы определяем переменную, которая будет экземпляром SomeClass. Также помимо этого мы создаем функцию конструктора, которая будет выполнена, когда мы с помощью ключевого слова new создадим экземпляр класса. Для лучшего понимания посмотрим на скомпилированный код из нашего примера:

Как видно из кода выше, переменной greeter присваивается функция конструктора. Затем с помощью new эта функция вызывается и таким образом создается экземпляр класса. Функция конструктора также будет содержать все статические члены класса. Можно сказать, что класс состоит из статической части и части экземпляра класса.

Использование класса в качестве интерфейса

Поскольку класс представляет собой тип данных с определенной структурой, можно использовать его так, как будто это интерфейс. Например:

Ссылки

--

--