Классы в JavaScript: сахар над прототипами
Содержание
О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».
Классы появились в ECMAScript 6 и сделали ООП в JavaScript похожим на другие языки. Но важно понимать: это удобная запись поверх прототипов, а не новый механизм.
Базовый класс
class User {
constructor(name, age) {
this.name = name; // this — создаваемый экземпляр
this.age = age;
}
greet() {
return `Привет, я ${this.name}`;
}
}
const anna = new User('Аня', 30);
anna.greet(); // "Привет, я Аня"
constructorвызывается приnewи настраивает новый объект.thisвнутри него — создаваемый экземпляр (правилоnew);- методы (
greet) лежат в прототипе и общие для всех экземпляров — не копируются в каждый объект; - вызов через
newобязателен: без негоthisбудет не тем.
Это ровно то, что в прототипах писали руками через функцию-конструктор и prototype — класс просто спрятал эти детали.
Наследование: extends и super
class Admin extends User {
constructor(name, age, level) {
super(name, age); // вызвать конструктор родителя — ОБЯЗАТЕЛЬНО
this.level = level;
}
greet() {
return super.greet() + ` (админ уровня ${this.level})`; // достроить метод родителя
}
}
const boss = new Admin('Борис', 40, 3);
boss.greet(); // "Привет, я Борис (админ уровня 3)"
Две роли super:
super(...)в конструкторе вызывает конструктор родителя. В наследнике он обязателен до первого обращения кthis— иначеReferenceError. Это не формальность: родитель настраивает базовую часть объекта;super.method()вызывает метод родителя. Это способ достроить поведение, а не заменить: сначала родительскийgreet, потом своя добавка.
Приватные поля: настоящая приватность
Долгое время приватность в JavaScript была соглашением: подчёркивание в имени (_secret) означало «не трогай», но технически поле оставалось доступным. С приватными полями это изменилось:
class Account {
#balance = 0; // приватное поле
deposit(sum) { this.#balance += sum; }
getBalance() { return this.#balance; }
}
const acc = new Account();
acc.deposit(100);
acc.getBalance(); // 100
acc.#balance; // SyntaxError — снаружи не достать!
Поле с префиксом # доступно только внутри класса. Обращение к #balance снаружи — не undefined, а синтаксическая ошибка. Это настоящая инкапсуляция, встроенная в язык, — та же цель, что раньше достигали замыканиями, но привычнее для ООП.
Публичное
Доступно снаружи
- Обычные поля:
this.name - Методы:
greet() - Геттеры/сеттеры:
get,set - Часть контракта класса
Приватное (#)
Только внутри класса
- Поля:
#balance - Методы:
#validate() - Снаружи — SyntaxError
- Внутренняя реализация, можно менять свободно
Статические методы и поля
class User {
static count = 0;
constructor(name) {
this.name = name;
User.count++; // считаем всех пользователей
}
static fromJSON(json) { // фабрика
return new User(JSON.parse(json).name);
}
}
User.count; // сколько создано
const u = User.fromJSON('{"name":"Вера"}');
static принадлежит классу, а не экземпляру. Вызывается как User.fromJSON(), а не user.fromJSON(). Применяют для фабрик (альтернативные способы создать экземпляр), счётчиков и утилит, связанных с классом, но не с конкретным объектом.
Геттеры и сеттеры
class Circle {
constructor(radius) { this.radius = radius; }
get area() { // читается как свойство, а не метод
return Math.PI * this.radius ** 2;
}
set diameter(d) {
this.radius = d / 2;
}
}
const c = new Circle(5);
c.area; // вычисляется при обращении — без ()
c.diameter = 20; // запускает сеттер, radius станет 10
get и set дают вычисляемые свойства: снаружи c.area выглядит как обычное поле, но за ним вычисление. Удобно для производных значений и валидации при записи.
Нужны ли классы вообще
Честный вопрос, потому что JavaScript прекрасно живёт и без них. Правило простое:
- простые данные — обычный объект или массив, класс избыточен;
- поведение без состояния — функции и замыкания;
- классы оправданы, когда есть несколько экземпляров с общим поведением и своим состоянием, наследование или когда команда работает в ООП-стиле.
Как и везде на этом сайте: инструмент под задачу. Класс ради одного объекта — это церемония. Класс для сущности, которых много и у каждой своё состояние, — правильный выбор.
Смежные темы: прототипы — что под капотом класса; объекты — когда класс не нужен; замыкания — старый способ приватности. Полный список — в уроках JavaScript.