JavaScript / 38 минут
Прототипы и классы
Поймите прототипную цепочку, методы классов и границы инкапсуляции.
- Поймите прототипную цепочку, методы классов и границы инкапсуляции.
- Применить тему в задаче «Прототипы и классы» и объяснить граничные случаи.
Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Свойства экземпляра и цепочка прототипов
Объект может получать свойства не только из собственного набора, но и через прототипную цепочку. Если собственного свойства нет, поиск продолжается у прототипа, затем у его прототипа и так далее до конца цепочки. Это механизм делегирования поведения, а не обязательное копирование всех методов в каждый экземпляр. Проверка Object.hasOwn помогает отличить собственные данные от унаследованного доступа.
Класс предоставляет удобный синтаксис создания объектов с общим поведением. Он не отменяет прототипную модель JavaScript. Методы, объявленные в теле класса обычным синтаксисом метода, обычно находятся в prototype и доступны экземплярам через цепочку. Поля экземпляра, напротив, принадлежат каждому созданному объекту отдельно.
class Learner {
constructor(name) { this.name = name; }
greeting() { return `Привет, ${this.name}`; }
}
const learner = new Learner('Анна');
console.log(Object.hasOwn(learner, 'name')); // true
console.log(Object.hasOwn(learner, 'greeting')); // falseКонструктор и состояние объекта
new создаёт экземпляр, связывает его с прототипом и запускает constructor с подходящим this. Конструктор задаёт начальное корректное состояние. Если объект не имеет смысла без имени или положительного лимита, это правило полезно выразить при создании, а не ждать случайного сбоя через несколько методов. Однако внешнюю runtime-валидацию ввода всё равно проводят на границе приложения.
class Wallet {
constructor(balance = 0) { this.balance = balance; }
deposit(amount) {
if (amount > 0) this.balance += amount;
}
}
const wallet = new Wallet(10);
wallet.deposit(5);
console.log(wallet.balance); // 15Метод deposit изменяет конкретный экземпляр через this.balance. Второй кошелёк не должен использовать те же данные случайно: начальное состояние создаётся для каждого new. Если хранить balance как глобальную переменную модуля, все экземпляры потеряют независимость. Выбирайте расположение состояния исходя из того, кому оно принадлежит и как долго должно жить.
Публичные и приватные поля
Обычное поле доступно внешнему коду. Поле с # имеет специальную runtime-семантику приватности: произвольное чтение снаружи класса не разрешено. Это отличается от соглашения называть поле _balance и от статического TypeScript private. Публичный метод может предоставлять ограниченную операцию над приватным состоянием, сохраняя инварианты объекта.
class Counter {
#value = 0;
increment() { this.#value += 1; }
get value() { return this.#value; }
}
const counter = new Counter();
counter.increment();
console.log(counter.value); // 1Приватное поле не делает объект подходящим хранилищем серверного секрета в опубликованном клиентском коде. Код приложения и данные, переданные клиенту, находятся под контролем пользователя его среды. Инкапсуляция помогает управлять изменениями внутри программы, а реальная граница доверия между клиентом и сервером требует серверной проверки.
Наследование и super
extends связывает классы отношением наследования. Подкласс может переиспользовать поведение и переопределять методы. В конструкторе подкласса нужно вызвать super до обращения к this. Используйте наследование, когда отношение действительно соответствует взаимозаменяемости контрактов, а не только потому, что два класса имеют несколько похожих строк.
class Message {
constructor(text) { this.text = text; }
render() { return this.text; }
}
class ImportantMessage extends Message {
render() { return `Важно: ${super.render()}`; }
}Большая иерархия делает изменения связанных классов дорогими. Часто полезнее композиция: объект получает отдельную зависимость для хранения, форматирования или отправки и использует её контракт. Позже этот же принцип будет важен в Express-сервисах и DI NestJS. Общий код сам по себе не является достаточным основанием для общего предка.
Статические методы и контекст вызова
static-метод принадлежит самому классу, а не экземпляру. Он удобен для операции, которая не использует состояние конкретного объекта, например фабрики из DTO. Не путайте класс Account и объект account: доступные у них свойства различаются. Метод экземпляра, переданный отдельным callback, может потерять нужный this так же, как метод обычного объекта.
Выбор между классом, замыканием и набором чистых функций определяется моделью задачи. Класс полезен при явной сущности с состоянием и поведением; замыкание удобно для небольшой закрытой фабрики; чистая функция часто лучше для расчёта без собственного жизненного цикла. Не используйте классы как обязательную обёртку над каждой строкой кода.
Практическая модель счёта
В задании создаётся Account с начальным балансом. Каждая положительная сумма увеличивает его, ноль и отрицательные значения игнорируются по условию. Это учебное правило конкретного метода deposit, а не универсальная банковская модель. Реальное списание и зачисление потребовало бы отдельных доменных операций и правил точности денег.
Проверьте отсутствие операций, одну положительную сумму и смесь знаков. Создайте новый экземпляр при каждом solve. После решения объясните, где хранится balance, почему метод общий для экземпляров, но данные раздельные, и как изменился бы публичный контракт при переходе на приватное #balance с getter. Важно сохранить поведение, а не просто заменить видимость поля.