Классы и прототипы
class, приватные поля, геттеры, extends и что под капотом.
Обновлено
Что такое класс
Класс — это шаблон для создания однотипных объектов. Он один раз описывает, какие у них данные (свойства) и поведение (методы); затем вы создаёте сколько угодно экземпляров через new.
class Account {
constructor(owner) { // выполняется при new — инициализирует объект
this.owner = owner
this.balance = 0
}
deposit(amount) { // метод — общее поведение всех счетов
this.balance += amount
}
}
const a = new Account('Ana') // экземпляр
const b = new Account('Ivan') // другой экземпляр со своими данными
a.deposit(100)Аналогия: класс — это формочка, экземпляры — печенье.
Части класса — виды членов
| Член | Синтаксис | Принадлежит | Когда |
|---|---|---|---|
| публичное поле | count = 0 |
каждому экземпляру | данные, видимые снаружи |
| приватное поле | #balance = 0 |
каждому экземпляру, недоступно снаружи | внутренние защищённые данные |
| метод | deposit() {} |
общий для всех экземпляров | поведение |
| геттер / сеттер | get total() {} / set total(v) {} |
экземпляру | вычисляемое значение, читаемое как свойство |
| static | static from(json) {} |
классу, а не экземпляру | фабрики, константы, хелперы |
class Account {
#balance = 0 // приватное — acc.#balance даёт SyntaxError
static currency = 'RUB' // Account.currency
constructor(owner) { this.owner = owner }
deposit(amount) {
if (amount <= 0) throw new Error('Неверная сумма')
this.#balance += amount
return this // позволяет цепочку: acc.deposit(1).deposit(2)
}
get balance() { return this.#balance } // acc.balance — без скобок
static fromJSON({ owner }) { return new Account(owner) }
}Наследование
extends создаёт класс, который берёт всё у другого и что-то добавляет или меняет:
class Savings extends Account {
constructor(owner, rate) {
super(owner) // вызывает конструктор родителя — обязательно до this
this.rate = rate
}
addInterest() { this.deposit(this.balance * this.rate) }
}Прототипы — что под капотом
Классы — «синтаксический сахар» над прототипами. У каждого объекта есть скрытая связь с другим объектом (его прототипом). Если запросить свойство, которого нет у объекта, JS ищет его в прототипе, затем в прототипе прототипа — это цепочка прототипов.
a.deposit // не у a, найден в Account.prototype
Object.getPrototypeOf(a) === Account.prototype // true
a instanceof Account // true — Account.prototype есть в цепочкеПоэтому методы общие (один deposit в памяти на все счета), а [].map работает на любом массиве (он лежит в Array.prototype).
Классы или функции + объекты
| Классы | Функции + простые объекты | |
|---|---|---|
| Защищённое внутреннее состояние | #private |
замыкание |
| Наследование | extends |
композиция (комбинируете функции) |
this |
да, со всеми ловушками | не нужен |
| В современном React | только error boundaries | всё: компоненты, хуки |
Когда использовать классы во фронтенде
- Да: свои ошибки (
class HttpError extends Error), API-клиент / SDK, структуры данных (очередь, кэш), интеграция с библиотеками на классах. - Нет: React-компоненты (сегодня это функции), простые данные (хватит литерала объекта).
- Предпочитайте композицию (маленькие части вместе) глубоким иерархиям наследования —
A extends B extends C extends Dтрудно менять.
Коротко
- Класс = шаблон;
newсоздаёт экземпляры;constructorинициализирует. - Поля
#private, геттеры,static,extends+super. - Под капотом — прототипы. В React почти всегда функции.