Дополнительные возможности классов

Дополнительные возможности классов

Технический фундамент расширенных возможностей

Расширенные возможности классов усиливают модель, если использовать их по назначению:

  • static для поведения уровня класса;
  • #private для защиты внутреннего состояния;
  • instanceof для проверок по прототипной цепочке;
  • сериализация для передачи данных без методов.

Общий принцип: применять эти механики там, где они повышают надежность контрактов, а не просто "усложняют" класс.

Зачем нужны "дополнительные" возможности

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

Ключевой момент: эти возможности не заменяют базовую архитектуру, а усиливают ее там, где это действительно полезно.

Проверь себя: почему сначала важно освоить базовые классы, а потом уже расширенные механики?

Статические методы и свойства (static)

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

class Currency {
  static defaultCode = 'USD';

  static format(value) {
    return `${value} ${Currency.defaultCode}`;
  }
}

console.log(Currency.format(100));

Смотри, что важно:

  • вызывать нужно через имя класса (Currency.format(...));
  • на экземпляре (new Currency()) этого метода не будет.

Static удобно использовать для фабрик, утилит и общих констант класса.

Смотри, что важно: если ты ожидаешь наследование, внутри static-метода часто лучше ссылаться на this, а не на конкретное имя класса. Тогда переопределение static-полей в наследниках будет работать предсказуемо.

class Currency {
  static defaultCode = 'USD';
  static format(value) {
    return `${value} ${this.defaultCode}`;
  }
}

class EurCurrency extends Currency {
  static defaultCode = 'EUR';
}

console.log(EurCurrency.format(10)); // 10 EUR

Приватные поля (#)

JavaScript поддерживает настоящие приватные поля через #.

class BankAccount {
  #balance = 0;

  deposit(amount) {
    if (amount > 0) this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

Новый термин: приватное поле - поле, доступное только внутри класса. Снаружи account.#balance вызовет ошибку синтаксиса.

Это усиливает инкапсуляцию: никто извне не может случайно сломать внутреннее состояние.

Смотри, что важно: приватные поля принадлежат конкретному классу. Наследник не может напрямую читать/писать #balance родителя, доступ идет только через методы родителя.

class A {
  #x = 1;
  getX() {
    return this.#x;
  }
}

class B extends A {
  // this.#x; // SyntaxError
}

Проверь себя: чем #balance отличается от соглашения _balance?

Проверка типа через instanceof

Иногда нужно убедиться, что объект создан нужным классом.

class User {}
class Admin extends User {}

const admin = new Admin();
console.log(admin instanceof Admin); // true
console.log(admin instanceof User); // true

instanceof полезен в обработчиках, где поведение зависит от типа сущности.

Здесь часто путаются: instanceof проверяет связь по прототипной цепочке, а не просто совпадение названия класса.

Смотри, что важно: instanceof может быть ненадежен на границах окружений (например, объект пришел из другого iframe/бандла или библиотека подключена дважды). В таких местах иногда лучше проверять "форму" объекта (наличие нужных методов/полей) или использовать явные поля/брендинг в контракте.

Object.keys, сериализация и классы

У экземпляров класса сериализуются только собственные перечислимые поля. Методы в JSON не попадут.

class Task {
  constructor(title) {
    this.title = title;
    this.done = false;
  }

  complete() {
    this.done = true;
  }
}

const task = new Task('Проверить отчет');
console.log(JSON.stringify(task)); // {"title":"Проверить отчет","done":false}

Смотри, что важно:

  • Object.keys(task) покажет только собственные перечислимые поля (например, title, done).
  • методы/геттеры на прототипе в keys не попадут.
  • приватные поля #... тоже не сериализуются.

Иногда удобно явно управлять JSON-представлением через toJSON().

class Task {
  constructor(title) {
    this.title = title;
    this.done = false;
  }
  toJSON() {
    return { title: this.title, done: this.done };
  }
}

Дополнительный пример: статическая фабрика с приватным состоянием.

class Money {
  #amount;

  constructor(amount) {
    this.#amount = amount;
  }

  static fromString(raw) {
    const value = Number(raw);
    if (!Number.isFinite(value)) throw new Error('Некорректная сумма');
    return new Money(value);
  }

  get value() {
    return this.#amount;
  }
}

Это важно при сохранении данных в БД или localStorage: поведение класса придется восстанавливать отдельно.

Микро-сценарии из продукта

  1. Класс Money.
  • static fromString('1200') создает экземпляр из строки;
  • приватное поле хранит внутреннее значение;
  • геттер возвращает формат для UI.
  1. Модель заказа.
  • static createDraft() быстро создает черновик;
  • приватные поля защищают внутренние шаги расчета;
  • instanceof помогает отличать Order от других моделей в обработчике.

Частые ошибки новичков

  • Путать static и экземплярные методы.
  • Пытаться читать #private-поле снаружи класса.
  • Использовать instanceof там, где достаточно проверки формы объекта.
  • Добавлять расширенные механики "для солидности", а не по реальной потребности.

Анти-провал: сначала реши задачу простым классом, и только потом добавляй static/#private, если они реально улучшают модель.

Что будет, если изменить входные данные

Если в deposit(amount) передать отрицательное число и убрать проверку, баланс уйдет в некорректное состояние. Даже приватное поле не защищает от плохой логики внутри методов. Защита должна быть и в доступе, и в правилах изменения данных.

Проверь себя: почему приватность поля не заменяет валидацию входов метода?

Краткий итог

  • static-члены относятся к классу, а не к экземплярам.
  • #private поля дают настоящую инкапсуляцию в JavaScript.
  • instanceof помогает проверять тип с учетом прототипной цепочки.
  • При сериализации экземпляра в JSON сохраняются данные, но не методы.
  • Дополнительные возможности классов полезны, когда усиливают надежность и читаемость, а не усложняют код ради формы.