Дополнительные возможности классов
Дополнительные возможности классов
Технический фундамент расширенных возможностей
Расширенные возможности классов усиливают модель, если использовать их по назначению:
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: поведение класса придется восстанавливать отдельно.
Микро-сценарии из продукта
- Класс
Money.
static fromString('1200')создает экземпляр из строки;- приватное поле хранит внутреннее значение;
- геттер возвращает формат для UI.
- Модель заказа.
static createDraft()быстро создает черновик;- приватные поля защищают внутренние шаги расчета;
instanceofпомогает отличатьOrderот других моделей в обработчике.
Частые ошибки новичков
- Путать
staticи экземплярные методы. - Пытаться читать
#private-поле снаружи класса. - Использовать
instanceofтам, где достаточно проверки формы объекта. - Добавлять расширенные механики "для солидности", а не по реальной потребности.
Анти-провал: сначала реши задачу простым классом, и только потом добавляй static/#private, если они реально улучшают модель.
Что будет, если изменить входные данные
Если в deposit(amount) передать отрицательное число и убрать проверку, баланс уйдет в некорректное состояние. Даже приватное поле не защищает от плохой логики внутри методов. Защита должна быть и в доступе, и в правилах изменения данных.
Проверь себя: почему приватность поля не заменяет валидацию входов метода?
Краткий итог
static-члены относятся к классу, а не к экземплярам.#privateполя дают настоящую инкапсуляцию в JavaScript.instanceofпомогает проверять тип с учетом прототипной цепочки.- При сериализации экземпляра в JSON сохраняются данные, но не методы.
- Дополнительные возможности классов полезны, когда усиливают надежность и читаемость, а не усложняют код ради формы.