Класс — это шаблон (чертёж) для создания объектов с общим состоянием и поведением. В ООП классы помогают выразить абстракции, скрывать детали реализации (инкапсуляция), строить иерархии через наследование и заменять поведение за счёт полиморфизма.
В JavaScript классы — это синтаксический сахар над прототипами. Класс не вводит новую модель объектов — он упрощает работу с уже существующей прототипной системой и делает код ближе к каноническому языку ООП.
Объявление и выражение класса
Есть два способа задать класс: объявление (class C {...}) и выражение (const C = class {...} или именованное выражение const C = class Named {...}).
class Point {
// Конструктор вызывается при создании экземпляра
constructor(x, y) {
this.x = x;
this.y = y;
}
// Метод прототипа
length() {
return Math.hypot(this.x, this.y);
}
}
const p = new Point(3, 4);
console.log(p.length()); // 5
Конструктор и создание экземпляров
Конструктор — это специальный метод constructor, который инициализирует состояние. Экземпляры создаются оператором new.
constructor может принимать аргументы и устанавливать поля экземпляра.
В производном классе вызов super(...) обязателен перед использованием this.
Возврат из конструктора не обязателен; если явно вернуть объект, он заменит создаваемый экземпляр.
class Base {
constructor(name) {
this.name = name;
}
}
class User extends Base {
constructor(name, role) {
super(name); // инициализирует Base-часть
this.role = role;
}
}
const u = new User('Alice', 'admin');
console.log(u.name, u.role); // Alice admin
Методы, поля и порядок инициализации
Класс поддерживает методы прототипа, а также поля экземпляра (публичные и приватные), задаваемые прямо в теле класса. Важен порядок инициализации:
У базового класса поля-инициализаторы выполняются перед телом constructor.
У производного — после вызова super() и до остального кода конструктора.
class Counter {
// публичное поле экземпляра
value = 0;
increment() {
this.value += 1;
}
}
const c = new Counter();
c.increment();
console.log(c.value); // 1
Приватные поля и методы
Приватные члены объявляются с помощью # и недоступны вне класса. Это реальная инкапсуляция на уровне языка.
static-члены принадлежат самому классу, а не экземплярам. Это удобное место для фабрик, констант, утилит.
class Color {
static named = {red: '#f00', green: '#0f0', blue: '#00f'};
constructor(hex) {
this.hex = hex;
}
static fromName(name) {
return new Color(Color.named[name]);
}
}
const red = Color.fromName('red');
console.log(red.hex); // #f00
Геттеры и сеттеры (свойства)
Геттеры/сеттеры объявляют вычисляемые свойства и позволяют валидировать присваивания.
class Person {
#_age = 0;
get age() {
return this.#_age;
}
set age(v) {
if (v < 0) throw new Error('Age must be non-negative');
this.#_age = v;
}
}
const p = new Person();
p.age = 30;
console.log(p.age); // 30
Наследование, super и переопределение
Наследование (extends) связывает подкласс с суперклассом. Переопределяйте методы для реализации полиморфизма.
Класс — это функция особого вида; его методы — это свойства его prototype. Цепочка такова: Child.prototype.__proto__ === Parent.prototype и Child.__proto__ === Parent.
class A {
}
class B extends A {
}
console.log(Object.getPrototypeOf(B) === A); // true
console.log(Object.getPrototypeOf(B.prototype) === A.prototype); // true
Контекст this, стрелочные поля-методы и производительность
this в методах прототипа определяется вызовом. Для колбэков используйте bind или стрелочные поля-методы (они замыкают лексический this), но помните о накладных расходах на экземпляр.
class Button {
// стрелочное поле создаёт функцию на каждом экземпляре
onClick = () => {
console.log(this.label);
};
constructor(label) {
this.label = label;
}
}
const b = new Button('OK');
setTimeout(b.onClick, 0); // OK
Композиция против наследования
Многие иерархии проще и надёжнее выразить через композицию (объект содержит другие объекты и делегирует им работу), а не через глубокое наследование.
Миксины добавляют функциональность классам без иерархий.
const Timestamped = (Base) => class extends Base {
touch() {
this.updatedAt = Date.now();
}
};
class Model {}
class User extends Timestamped(Model) {}
const u = new User();
u.touch();
console.log(!!u.updatedAt); // true
Абстрактные базовые классы и контракты (приёмы)
В чистом JS нет ключевого слова abstract, но контракт можно выразить через проверки new.target и «виртуальные» методы.
class Repo {
constructor() {
if (new.target === Repo) throw new Error('Use a concrete subclass');
}
// "виртуальный" метод
findById(id) {
throw new Error('Not implemented');
}
}
class MemoryRepo extends Repo {
findById(id) {
return {id};
}
}
console.log(new MemoryRepo().findById(1).id); // 1
Сериализация, клонирование и сравнение
Экземпляры классов при JSON.stringify теряют методы и приватные поля. Для переносимых представлений реализуйте toJSON() и статические фабрики/парсеры.