Поля, конструкторы и методы
Поля, конструкторы и методы
Объявление полей
В TypeScript каждое поле класса должно быть явно объявлено с указанием типа. Это фундаментальное отличие от JavaScript, где поля могут появляться динамически.
class Point {
// Явное объявление полей с типами
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
Инициализация полей
Поля можно инициализировать прямо при объявлении. В этом случае TypeScript выведет тип автоматически:
class Config {
// Тип выводится как number
timeout = 5000;
// Тип выводится как string
baseUrl = "https://api.example.com";
// Явная аннотация, когда вывод недостаточен
headers: Record<string, string> = {};
}
Строгая инициализация: strictPropertyInitialization
При включённом strictPropertyInitialization (часть strict) TypeScript требует, чтобы каждое поле было инициализировано в конструкторе или при объявлении:
class User {
name: string; // OK — инициализируем в конструкторе
email: string; // Ошибка! Не инициализировано
// Property 'email' has no initializer and is not definitely assigned in the constructor.
constructor(name: string) {
this.name = name;
}
}
Если вы уверены, что поле будет инициализировано извне (например, ORM или фреймворком), используйте оператор definite assignment assertion !:
class Entity {
// Говорим TypeScript: «Я гарантирую, что поле будет установлено»
id!: number;
}
Внимание:
!отключает проверку только для этого поля. Злоупотребление оператором сводит на нет пользу строгого режима.
Модификатор readonly
Поля с readonly можно присвоить только в конструкторе. После создания объекта они неизменяемы:
class Circle {
readonly radius: number;
constructor(radius: number) {
this.radius = radius;
}
// Ошибка! Нельзя изменить readonly-поле
scale(factor: number) {
// this.radius *= factor;
// Cannot assign to 'radius' because it is a read-only property.
// Вместо этого — создаём новый объект
return new Circle(this.radius * factor);
}
}
readonly — это проверка на этапе компиляции. В рантайме JavaScript поле остаётся обычным свойством. Если нужна настоящая неизменяемость — используйте Object.freeze() или #private поля.
Конструкторы
Конструкторы в TypeScript похожи на обычные функции с аннотациями типов, но у них есть особенности:
class Database {
private connection: string;
private port: number;
// Конструктор может иметь перегрузки
constructor(connectionString: string);
constructor(host: string, port: number);
constructor(hostOrConnection: string, port?: number) {
if (port !== undefined) {
this.connection = hostOrConnection;
this.port = port;
} else {
this.connection = hostOrConnection;
this.port = 5432; // порт по умолчанию
}
}
}
const db1 = new Database("postgres://localhost/mydb");
const db2 = new Database("localhost", 3306);
Конструкторы не могут иметь аннотацию возвращаемого типа — они всегда возвращают экземпляр класса.
Методы
Методы типизируются так же, как функции:
class Calculator {
private result = 0;
add(value: number): this {
this.result += value;
return this; // возвращаем this для цепочки вызовов
}
subtract(value: number): this {
this.result -= value;
return this;
}
getResult(): number {
return this.result;
}
}
const result = new Calculator()
.add(10)
.subtract(3)
.getResult(); // 7
Проблема потери this
Классическая ловушка: при передаче метода как колбэка теряется контекст this:
class Button {
label = "Click me";
// Обычный метод — this может потеряться
handleClick() {
console.log(this.label); // undefined при потере контекста!
}
// Стрелочная функция — this всегда привязан к экземпляру
handleClickSafe = () => {
console.log(this.label); // всегда "Click me"
};
}
const btn = new Button();
const handler = btn.handleClick;
handler(); // undefined — this потерян
const safeHandler = btn.handleClickSafe;
safeHandler(); // "Click me" — this сохранён
Компромисс: стрелочные методы создают новую функцию для каждого экземпляра (больше памяти), но гарантируют корректный
this. Обычные методы живут в прототипе (одна копия), но требуют.bind()при передаче.
Геттеры и сеттеры
TypeScript поддерживает аксессоры get и set с полной типизацией:
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
// Геттер — вычисляемое свойство
get fahrenheit(): number {
return this._celsius * 9 / 5 + 32;
}
// Сеттер — валидация при установке
set fahrenheit(value: number) {
this._celsius = (value - 32) * 5 / 9;
}
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Температура ниже абсолютного нуля невозможна");
}
this._celsius = value;
}
}
const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
Правила аксессоров в TypeScript
- Если есть
getбезset— свойство автоматическиreadonly - Если тип сеттера не указан — он выводится из типа геттера
- Геттер и сеттер должны иметь одинаковую видимость (
public,protected,private)
class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
// Только геттер — свойство readonly снаружи
get name(): string {
return this._name;
}
// С TypeScript 4.3+ сеттер может принимать более широкий тип
get id(): string {
return this._id;
}
private _id = "";
set id(value: string | number) {
this._id = String(value);
}
}
В JavaScript (и TypeScript) объявления полей класса создают собственные свойства экземпляра, а не свойства прототипа. Это принципиальное отличие от методов:
class Example {
field = 42; // Собственное свойство каждого экземпляра
method() {} // Свойство прототипа (одна копия на все экземпляры)
arrow = () => {}; // Собственное свойство (новая функция для каждого экземпляра)
}
const a = new Example();
const b = new Example();
a.method === b.method; // true — один и тот же объект-функция
a.arrow === b.arrow; // false — разные функции
Это влияет на потребление памяти: при создании тысяч экземпляров стрелочные методы и поля-функции занимают значительно больше памяти, чем обычные методы.