Backend Typescript 1.0.0 Help

Классы

Зачем нужны классы (связь с ООП)

Класс — это шаблон (чертёж) для создания объектов с общим состоянием и поведением. В ООП классы помогают выразить абстракции, скрывать детали реализации (инкапсуляция), строить иерархии через наследование и заменять поведение за счёт полиморфизма.

В JavaScript классы — это синтаксический сахар над прототипами. Класс не вводит новую модель объектов — он упрощает работу с уже существующей прототипной системой и делает код ближе к каноническому языку ООП.

Объявление и выражение класса

Есть два способа задать класс: объявление (class C {...}) и выражение (const C = class {...} или именованное выражение const C = class Named {...}).

class Point { // Конструктор вызывается при создании экземпляра constructor(x, y) { this.x = x; this.y = y; } // Метод прототипа length() { return Math.hypot(this.x, this.y); } } const p = new Point(3, 4); console.log(p.length()); // 5

Конструктор и создание экземпляров

Конструктор — это специальный метод constructor, который инициализирует состояние. Экземпляры создаются оператором new.

  • constructor может принимать аргументы и устанавливать поля экземпляра.

  • В производном классе вызов super(...) обязателен перед использованием this.

  • Возврат из конструктора не обязателен; если явно вернуть объект, он заменит создаваемый экземпляр.

class Base { constructor(name) { this.name = name; } } class User extends Base { constructor(name, role) { super(name); // инициализирует Base-часть this.role = role; } } const u = new User('Alice', 'admin'); console.log(u.name, u.role); // Alice admin

Методы, поля и порядок инициализации

Класс поддерживает методы прототипа, а также поля экземпляра (публичные и приватные), задаваемые прямо в теле класса. Важен порядок инициализации:

  • У базового класса поля-инициализаторы выполняются перед телом constructor.

  • У производного — после вызова super() и до остального кода конструктора.

class Counter { // публичное поле экземпляра value = 0; increment() { this.value += 1; } } const c = new Counter(); c.increment(); console.log(c.value); // 1

Приватные поля и методы

Приватные члены объявляются с помощью # и недоступны вне класса. Это реальная инкапсуляция на уровне языка.

class BankAccount { #balance = 0; // приватное поле static #fee = 0.01; // приватное статическое поле deposit(amount) { this.#balance += amount; } withdraw(amount) { this.#balance -= amount + amount * BankAccount.#fee; } get balance() { return this.#balance; } } const acc = new BankAccount(); acc.deposit(100); console.log(acc.balance); // 100 // console.log(acc.#balance); // Ошибка синтаксиса

Статические поля и методы

static-члены принадлежат самому классу, а не экземплярам. Это удобное место для фабрик, констант, утилит.

class Color { static named = {red: '#f00', green: '#0f0', blue: '#00f'}; constructor(hex) { this.hex = hex; } static fromName(name) { return new Color(Color.named[name]); } } const red = Color.fromName('red'); console.log(red.hex); // #f00

Геттеры и сеттеры (свойства)

Геттеры/сеттеры объявляют вычисляемые свойства и позволяют валидировать присваивания.

class Person { #_age = 0; get age() { return this.#_age; } set age(v) { if (v < 0) throw new Error('Age must be non-negative'); this.#_age = v; } } const p = new Person(); p.age = 30; console.log(p.age); // 30

Наследование, super и переопределение

Наследование (extends) связывает подкласс с суперклассом. Переопределяйте методы для реализации полиморфизма.

class Shape { area() { console.log("Родительский метод") return 0; } } class Rect extends Shape { constructor(w, h) { super(); this.w = w; this.h = h; } area() { super.area() return this.w * this.h; } // переопределение } class Square extends Rect { constructor(size) { super(size, size); } } console.log(new Rect(3, 4).area()); // 12 console.log(new Square(5).area()); // 25

Классы и прототипы: что под капотом

Класс — это функция особого вида; его методы — это свойства его prototype. Цепочка такова: Child.prototype.__proto__ === Parent.prototype и Child.__proto__ === Parent.

class A { } class B extends A { } console.log(Object.getPrototypeOf(B) === A); // true console.log(Object.getPrototypeOf(B.prototype) === A.prototype); // true

Контекст this, стрелочные поля-методы и производительность

this в методах прототипа определяется вызовом. Для колбэков используйте bind или стрелочные поля-методы (они замыкают лексический this), но помните о накладных расходах на экземпляр.

class Button { // стрелочное поле создаёт функцию на каждом экземпляре onClick = () => { console.log(this.label); }; constructor(label) { this.label = label; } } const b = new Button('OK'); setTimeout(b.onClick, 0); // OK

Композиция против наследования

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

const withLogging = (target) => ({ ...target, log(msg) { console.log(`[log] ${msg}`); } }); const service = withLogging({ fetch() { return 'data'; } }); console.log(service.fetch()); // data service.log('done'); // [log] done

Миксины (mixins)

Миксины добавляют функциональность классам без иерархий.

const Timestamped = (Base) => class extends Base { touch() { this.updatedAt = Date.now(); } }; class Model {} class User extends Timestamped(Model) {} const u = new User(); u.touch(); console.log(!!u.updatedAt); // true

Абстрактные базовые классы и контракты (приёмы)

В чистом JS нет ключевого слова abstract, но контракт можно выразить через проверки new.target и «виртуальные» методы.

class Repo { constructor() { if (new.target === Repo) throw new Error('Use a concrete subclass'); } // "виртуальный" метод findById(id) { throw new Error('Not implemented'); } } class MemoryRepo extends Repo { findById(id) { return {id}; } } console.log(new MemoryRepo().findById(1).id); // 1

Сериализация, клонирование и сравнение

Экземпляры классов при JSON.stringify теряют методы и приватные поля. Для переносимых представлений реализуйте toJSON() и статические фабрики/парсеры.

class Point { constructor(x, y) { this.x = x; this.y = y; } toJSON() { return {x: this.x, y: this.y, _type: 'Point'}; } static fromJSON(o) { return new Point(o.x, o.y); } } const json = JSON.stringify(new Point(1, 2)); console.log(json.includes('_type')); // true const p = Point.fromJSON(JSON.parse(json)); console.log(p instanceof Point); // true

Расширенные возможности

  • static блоки — один раз инициализируют статическое состояние класса.

  • Symbol.hasInstance — позволяет настроить поведение instanceof.

  • Асинхронные методы (async) и статические асинхронные фабрики.

class Tokenizer { static patterns; static { Tokenizer.patterns = [/\\w+/, /\\s+/]; } static [Symbol.hasInstance](obj) { return !!obj && typeof obj.next === 'function'; } } console.log({ next() { } } instanceof Tokenizer); // true

Лучшие практики и частые ошибки

  • Не делайте тяжёлую работу в constructor (I/O, сетевые вызовы). Вынесите в статические фабрики или методы инициализации.

  • Предпочитайте композицию глубокой иерархии наследования.

  • Фиксируйте инварианты приватными полями и валидаторами в сеттерах.

  • Явно документируйте контракты (ожидаемые методы/свойства), особенно при использовании миксинов.

  • Не полагайтесь на перечислимость методов — используйте явные API.

Last modified: 01 October 2025