Классы в TypeScript
Класс в TypeScript — это класс ES2015 плюс слой типов и контроля доступа поверх него. Всё runtime-поведение (поля, constructor, методы, static, наследование через прототипы) — обычный JavaScript. Всё, что добавляет TypeScript — типы полей, модификаторы доступа public/private/protected, readonly, abstract — живёт только на этапе компиляции и исчезает из итогового кода. Понимание этой границы «что реально в рантайме, а что лишь проверка компилятора» — главный водораздел темы.
Отсюда — главная ловушка новичка: принимать private и readonly за рантайм-гарантии. Это не так. private-поле спокойно читается из скомпилированного JavaScript, а readonly-массив по-прежнему принимает push. Настоящую приватность в рантайме даёт только ECMAScript-поле #field. Ниже — семь слоёв: от базовых членов класса до абстрактных классов.
Карта темы
- Классы и члены — типизированные поля,
constructor, методы,static-члены; что TypeScript добавляет к классам ES2015. - Модификаторы доступа —
public/private/protected, почему они стираются в рантайме и зачем нужен#field. - readonly-поля — присваивание один раз, только при компиляции, закрепляет привязку, а не содержимое.
- Аксессоры (get/set) — перехват чтения и записи свойства, вычисляемые свойства, геттер без сеттера.
- Параметр-свойства — сокращение конструктора, что запускает поле, только в
constructor. - Наследование —
extends,super()доthis,override, только одиночное наследование. - Абстрактные классы — неинстанцируемый чертёж с реализацией и состоянием,
abstract-методы, отличие от интерфейса.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать private/readonly рантайм-гарантией | Оба стираются при компиляции; поле читается и мутируется из JavaScript |
Смешивать private и ECMAScript-поле #field | Только #field реально приватен в рантайме; private — дисциплина этапа компиляции |
Обращаться к static-члену через экземпляр | Его там нет — static живёт на классе, а не на экземпляре |
Ждать, что у наследника своя копия static-поля | static — один слот на всю иерархию; Child.count++ меняет и Base.count |
Путать private и protected | Доступ из производного класса — это protected, а не private |
Ждать, что readonly-массив нельзя менять | Закреплена лишь привязка; push по-прежнему работает — для содержимого нужен readonly T[] |
| Вызывать аксессор со скобками | obj.name, а не obj.name() — иначе получите функцию, а не значение |
| Ждать, что геттер без сеттера создаст рантайм-иммутабельность | Свойство только для чтения на этапе компиляции; в рантайме дескриптор можно переопределить |
| Думать, что обычный параметр станет полем | Поле создаёт только модификатор или readonly перед параметром |
Дублировать this.x = x рядом с параметр-свойством | Присваивание уже сгенерировано — ручное дублирование избыточно |
Ждать параметр-свойства вне constructor | Механизм работает только в конструкторе, не в обычных методах |
Обращаться к this до super(...) | Ошибка компиляции и рантайма в конструкторе производного класса |
Переопределять метод без override при noImplicitOverride | Опечатка в имени создаст новый метод вместо переопределения — флаг это ловит |
Ждать множественного наследования (extends A, B) | Только одиночное наследование; несколько контрактов — через implements |
Пытаться создать new абстрактного класса | Абстрактный класс нельзя инстанцировать напрямую |
Оставить abstract-метод нереализованным в наследнике | Наследник сам обязан стать abstract, иначе — ошибка компиляции |
Значение для собеседований
Классы по TypeScript спрашивают почти всегда — и проверяют не синтаксис, а понимание границы между системой типов и рантаймом. Кандидат, который говорит «private — это проверка компилятора, она стирается, а настоящая приватность — #field», сразу выделяется на фоне «private прячет поле».
Что обычно проверяют:
- Какие члены даёт класс — типизированные поля, методы,
static,readonly, аксессоры — и что из этого добавляет TypeScript поверх ES2015. - Разницу
public/private/protected, то, что они работают только при компиляции, и чем#fieldотличается отprivate. - Что
static-поле — один общий слот на всю иерархию, а не копия у каждого наследника. - Что
readonlyзакрепляет привязку, а не содержимое, и что он тоже стирается. - Как
get/setперехватывают доступ и когда геттер делает свойство только для чтения. - Что параметр-свойство создаётся модификатором, а не самим фактом параметра, и только в
constructor. - Правило
super()доthis, рольoverrideприnoImplicitOverrideи почему наследование только одиночное. - Чем абстрактный класс отличается от интерфейса — реализацией, состоянием и наличием в рантайме.
Типичный неверный ответ: «private физически скрывает поле». Это открывает разговор о том, что модификаторы доступа — инструмент дисциплины на этапе компиляции, а не защита в рантайме, и что для реальной приватности есть #field. Второй частый провал — считать readonly-массив неизменяемым: сама привязка закреплена, но push в него по-прежнему проходит, потому что readonly защищает слот, а не то, что в нём лежит.