Урок 25: Классы — свойства, конструктор, методы в TypeScript

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

Почему он так важен? Потому что классы это краеугольный камень объектно-ориентированного программирования (ООП). Они позволяют структурировать код, создавать сложные, многократно используемые сущности и строить по-настоящему масштабируемые приложения. TypeScript, будучи надмножеством JavaScript, предоставляет потрясающие возможности для работы с классами, добавляя к ним строгую типизацию, которой так не хватает в чистом JS.

В этом уроке мы детально разберем синтаксис классов в TypeScript, научимся объявлять свойства, познакомимся с конструктором, специальным методом для инициализации объектов и создадим наши первые методы. Всё это мы будем подкреплять практическими примерами и задачами.

Синтаксис классов в TypeScript

Давай начнем с самого простого, с того, как вообще объявить класс. Синтаксис TypeScript для классов очень похож на современный JavaScript (ES6 и выше), но с одним критически важным дополнением, аннотациями типов.

Базовый синтаксис объявления класса выглядит так:

typescript
class ClassName {
  // свойства класса (поля)
  propertyName: type;

  // конструктор
  constructor(parameters) {
    // инициализация свойств
  }

  // методы класса
  methodName(parameters): returnType {
    // тело метода
  }
}

Ключевое слово class сообщает компилятору, что мы начинаем объявление нового класса. За ним следует имя класса. По общепринятому соглашению, имена классов в TypeScript и JavaScript пишутся в PascalCase (каждое слово с заглавной буквы), чтобы отличать их от переменных и функций, которые обычно используют camelCase.

Давай создадим наш первый, максимально простой класс. Представь, что мы пишем приложение для зоопарка. Создадим класс Animal.

typescript
class Animal {
  // Пока что класс пустой, но это уже валидный код.
}

Теперь мы можем создавать экземпляры (объекты) этого класса с помощью оператора new:

typescript
let lion = new Animal();
let tiger = new Animal();
console.log(lion); // Animal {}
console.log(tiger); // Animal {}

lion и tiger это два разных объекта, созданных по одному и тому же «чертежу», классу Animal. Пока наш класс не делает ничего полезного, давай это исправлять, добавляя ему свойства.

Аннотация типов для свойств класса: описываем состояние объекта

Свойства класса (их также называют полями) это переменные, которые принадлежат каждому экземпляру класса. Они описывают состояние объекта. У каждого животного в нашем зоопарке есть имя, возраст и вид. Давай опишем это в классе.

В TypeScript мы обязаны явно объявить все свойства класса и указать их типы. Это можно сделать двумя основными способами:

  1. Явное объявление перед конструктором.

  2. Объявление и инициализация прямо в конструкторе (с использованием параметров свойств).

Сначала рассмотрим первый, самый распространенный способ.

Мы перечисляем свойства в теле класса, до определения конструктора и методов.

typescript
class Animal {
  // Объявление свойств с аннотациями типов.
  name: string;
  age: number;
  species: string;
  isMammal: boolean; // новое свойство

  // Конструктор пока пустой
  constructor() {}
}

Обрати внимание: мы только объявили свойства, но не инициализировали их. Если мы сейчас создадим объект, то свойства будут иметь специальное значение undefined.

typescript
let unknownAnimal = new Animal();
console.log(unknownAnimal.name); // undefined
console.log(unknownAnimal.age); // undefined

В чистом JavaScript это норма, но TypeScript, в своем строгом режиме (strict: true, который настоятельно рекомендуется), будет ругаться на попытку использовать неинициализированное свойство. Компилятор скажет нам: Property 'name' has no initializer and is not definitely assigned in the constructor. Он переживает, что мы забыли присвоить значение и мы можем столкнуться с ошибкой во время выполнения.

Как же нам правильно проинициализировать свойства? Для этого существует волшебный метод, конструктор.

Конструктор класса: место, где рождается объект

Конструктор это специальный метод класса, который автоматически вызывается при создании нового экземпляра класса с помощью оператора new. Его основная задача инициализировать объект, то есть присвоить начальные значения его свойствам.

В TypeScript конструктор объявляется с помощью ключевого слова constructor. Он может принимать параметры, которые мы передаем при вызове new ClassName(...).

Давай модифицируем наш класс Animal, добавив конструктор, который будет принимать значения для свойств и присваивать их.

typescript
class Animal {
  name: string;
  age: number;
  species: string;
  isMammal: boolean;

  // Конструктор с параметрами
  constructor(animalName: string, animalAge: number, animalSpecies: string, mammalStatus: boolean) {
    // Инициализация свойств класса
    this.name = animalName;
    this.age = animalAge;
    this.species = animalSpecies;
    this.isMammal = mammalStatus;

    console.log(`Новое животное ${this.name} создано!`);
  }
}

Разберем каждую строчку:

  • constructor(animalName: string, ...) мы объявляем конструктор, который ожидает четыре аргумента с конкретными типами.

  • this ключевое слово this внутри класса ссылается на текущий экземпляр класса. То есть на тот конкретный объект, который создается в данный момент.

  • this.name = animalName; мы берем параметр animalName, который передали в конструктор и присваиваем его свойству name текущего объекта (this).

Теперь мы можем создавать животных осмысленно:

typescript
// Передаем аргументы в конструктор
let myLion = new Animal("Симба", 5, "Лев", true);
let myFrog = new Animal("Кваки", 1, "Лягушка", false);

console.log(myLion); // Animal { name: 'Симба', age: 5, species: 'Лев', isMammal: true }
console.log(myFrog.name); // "Кваки"
console.log(myFrog.isMammal); // false

Гораздо лучше! Теперь у каждого животного есть уникальные характеристики, заданные в момент создания.

Сокращенная инициализация: параметры свойств

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

Идея в том, чтобы добавить модификатор доступа (например, publicprivatereadonly) прямо к параметру конструктора. Компилятор TS автоматически создаст свойство класса с таким же именем и присвоит ему значение из параметра.

Перепишем наш класс с использованием этой возможности:

typescript
class Animal {
  // Заметь: мы больше не объявляем свойства здесь явно!

  // В параметрах конструктора добавляем модификаторы
  constructor(
    public name: string,
    public age: number,
    public species: string,
    public isMammal: boolean
  ) {
    // Тело конструктора теперь может быть пустым!
    // this.name = name; и т.д. происходит автоматически.
    console.log(`Новое животное ${this.name} создано!`);
  }
}

Этот код делает ровно то же самое, что и предыдущий. Компилятор сам создаст свойства nameagespecies и isMammal для класса Animal и проинициализирует их в конструкторе. Этот синтаксис чище и используется очень часто.

Важное примечание: Если ты не укажешь модификатор (publicprivate и т.д.), параметр name будет считаться просто локальной переменной конструктора, а не свойством класса. Поэтому модификатор обязателен для работы этого синтаксиса.

Методы класса: определяем поведение объекта

Если свойства описывают состояние объекта (какие данные он хранит), то методы описывают его поведение (что он может делать). Методы класса это просто функции, объявленные внутри класса и имеющие доступ к его свойствам и другим методам через this.

Синтаксис объявления метода очень похож на объявление функции, но без ключевого слова function.

Давай добавим нашему животному несколько осмысленных действий, методов.

typescript
class Animal {
  constructor(
    public name: string,
    public age: number,
    public species: string,
    public isMammal: boolean
  ) {}

  // Метод, который возвращает строку с представлением животного
  getDescription(): string {
    return `${this.name} - это ${this.species} ${this.isMammal ? 'млекопитающее' : 'не млекопитающее'}. Возраст: ${this.age} лет.`;
  }

  // Метод, который "заставляет" животное постареть на 1 год
  haveBirthday(): void {
    this.age++; // Увеличиваем возраст на 1
    console.log(`С днем рождения, ${this.name}! Теперь тебе ${this.age}.`);
  }

  // Метод, который выводит звук животного (упрощенно)
  makeSound(sound: string = "неизвестный звук"): void {
    console.log(`${this.name} издает: ${sound}!`);
  }
}

Теперь наши животные ожили! Они умеют представляться, праздновать день рождения и издавать звуки.

Давай посмотрим на них в действии:

typescript
let simba = new Animal("Симба", 5, "Лев", true);
let quacky = new Animal("Кваки", 1, "Лягушка", false);

// Вызываем методы у созданных объектов
console.log(simba.getDescription()); // "Симба - это Лев млекопитающее. Возраст: 5 лет."
quacky.haveBirthday(); // "С днем рождения, Кваки! Теперь тебе 2."
console.log(quacky.age); // 2 - свойство действительно изменилось!

simba.makeSound("Рррррр!"); // "Симба издает: Рррррр!"
quacky.makeSound(); // "Кваки издает: неизвестный звук!" (использовано значение по умолчанию)

Обрати внимание, как внутри методов мы обращаемся к свойствам объекта: this.namethis.agethis гарантирует, что мы работаем с данными именно того экземпляра, у которого был вызван метод. Когда мы вызываем quacky.haveBirthday()this внутри метода haveBirthday указывает на объект quacky.

Практические задачи для закрепления

Давай превратим её в знания, выполнив несколько задач.

Задача 1: Класс «Пользователь»

Создай класс User, который представляет пользователя в системе.

  • Свойства: username (строка), email (строка), isOnline (булево, по умолчанию false).

  • Конструктор должен инициализировать все свойства, кроме isOnline, которое должно быть установлено в false при создании, если не передано иное.

  • Метод login(), который меняет статус isOnline на true и выводит сообщение в консоль ${username} вошел в систему..

  • Метод logout(), который меняет статус isOnline на false и выводит сообщение ${username} вышел из системы..

  • Метод getStatus(), который возвращает строку вида Пользователь ${username} (${email}) сейчас ${isOnline ? 'online' : 'offline'}..

Решение:

typescript
class User {
  isOnline: boolean;

  constructor(
    public username: string,
    public email: string,
    isOnline: boolean = false // Параметр по умолчанию
  ) {
    this.isOnline = isOnline;
  }

  login(): void {
    this.isOnline = true;
    console.log(`${this.username} вошел в систему.`);
  }

  logout(): void {
    this.isOnline = false;
    console.log(`${this.username} вышел из системы.`);
  }

  getStatus(): string {
    const status = this.isOnline ? 'online' : 'offline';
    return `Пользователь ${this.username} (${this.email}) сейчас ${status}.`;
  }
}

// Проверяем
let user1 = new User("max_dev", "max@yandex.ru");
let user2 = new User("anna_coder", "anna@google.com", true);

console.log(user1.getStatus()); // ... сейчас offline.
user1.login(); // max_dev вошел в систему.
console.log(user1.getStatus()); // ... сейчас online.

console.log(user2.getStatus()); // ... сейчас online.
user2.logout(); // anna_coder вышел из системы.

Задача 2: Класс «Прямоугольник»

Создай класс Rectangle.

  • Свойства: width (число), height (число).

  • Используй сокращенный синтаксис параметров свойств в конструкторе.

  • Реализуй метод getArea(), который вычисляет и возвращает площадь прямоугольника.

  • Реализуй метод getPerimeter(), который вычисляет и возвращает периметр прямоугольника.

  • Реализуй метод scale(factor: number), который увеличивает ширину и высоту прямоугольника в указанное количество раз.

Решение:

typescript
class Rectangle {
  constructor(
    public width: number,
    public height: number
  ) {}

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }

  scale(factor: number): void {
    this.width = this.width * factor;
    this.height = this.height * factor;
  }
}

// Проверяем
let rect = new Rectangle(5, 10);
console.log("Площадь:", rect.getArea()); // 50
console.log("Периметр:", rect.getPerimeter()); // 30

rect.scale(2);
console.log("Новая ширина:", rect.width); // 10
console.log("Новая высота:", rect.height); // 20
console.log("Новая площадь:", rect.getArea()); // 200

Задача 3: Класс «Банковский счет» (посложнее)

Создай класс BankAccount.

  • Свойства: accountNumber (строка, номер счета), balance (число, текущий баланс, по умолчанию 0), owner (строка, владелец счета).

  • Реализуй метод deposit(amount: number), который добавляет сумму к балансу и выводит сообщение о внесении.

  • Реализуй метод withdraw(amount: number), который вычитает сумму из баланса. Не позволяй снять больше средств, чем есть на счете. Выводи соответствующие сообщения об успешном снятии или ошибке.

  • Реализуй метод getBalance(), который возвращает текущий баланс.

Решение:

typescript
class BankAccount {
  balance: number = 0;

  constructor(
    public accountNumber: string,
    public owner: string,
    initialBalance: number = 0
  ) {
    this.balance = initialBalance;
  }

  deposit(amount: number): void {
    if (amount <= 0) {
      console.log("Сумма для депозита должна быть положительной.");
      return;
    }
    this.balance += amount;
    console.log(`На счет ${this.accountNumber} внесено ${amount}. Новый баланс: ${this.balance}`);
  }

  withdraw(amount: number): void {
    if (amount <= 0) {
      console.log("Сумма для снятия должна быть положительной.");
      return;
    }
    if (amount > this.balance) {
      console.log(`Недостаточно средств на счете ${this.accountNumber}. Запрошено: ${amount}, доступно: ${this.balance}`);
      return;
    }
    this.balance -= amount;
    console.log(`Со счета ${this.accountNumber} снято ${amount}. Новый баланс: ${this.balance}`);
  }

  getBalance(): number {
    return this.balance;
  }
}

// Проверяем
let myAccount = new BankAccount("DE91100000000123456789", "Максим", 100);
myAccount.deposit(50); // Внесено 50. Новый баланс: 150
myAccount.withdraw(75); // Снято 75. Новый баланс: 75
myAccount.withdraw(100); // Недостаточно средств... Доступно: 75
console.log("Текущий баланс:", myAccount.getBalance()); // 75

Заключение по уроку

Сегодня мы с тобой прошли гигантский пласт материала. Ты научился:

  1. Объявлять классы с помощью ключевого слова class.

  2. Явно объявлять свойства класса с аннотациями типов.

  3. Использовать конструктор для инициализации объектов. Это сердце класса, где задаются его начальные параметры.

  4. Применять сокращенный синтаксис Parameter Properties (public name: string в конструкторе) для более лаконичного кода.

  5. Создавать методы, функции которые определяют поведение объектов и работают с их внутренним состоянием через this.

В следующих уроках мы углубимся в более сложные концепции ООП в TypeScript: наследование, инкапсуляцию (модификаторы доступа privateprotected) и полиморфизм.

Попробуй создать свои классы: BookCarProduct. Чем больше практики, тем прочнее знания.

Если ты хочешь двигаться дальше и освоить TypeScript на все 100%, жду тебя на полном курсе, где мы разберем всё от А до Я.

Полный курс с уроками по TypeScript для начинающих: https://max-gabov.ru/typescript-dlya-nachinaushih

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий