$ sudo teach IT
РАЗДЕЛ 8 · УРОК 3

Классы (class)

До этого мы создавали объекты вручную — каждый раз заново писали все свойства и методы. Но что если нужны сотни одинаковых объектов? Классы решают эту проблему: они позволяют описать шаблон один раз и создавать по нему сколько угодно объектов через new.

⏱ ~30 минут 🎓 Для новичков ⚡ JavaScript

🤔 Проблема: сто одинаковых объектов

Представьте: вы делаете систему для интернет-магазина. У вас тысячи пользователей. Каждый пользователь — объект с именем, email, балансом и несколькими методами. Создавать их вручную — безумие:

// БЕЗ класса — так нельзя масштабировать!
const user1 = { name: "Анна", email: "a@mail.ru", balance: 0, ... };
const user2 = { name: "Борис", email: "b@mail.ru", balance: 0, ... };
const user3 = { name: "Вера", email: "v@mail.ru", balance: 0, ... };
// ... ещё 997 таких же строк?

Это ужасно — дублирование кода, высокий риск ошибки. Класс решает задачу: описываешь структуру один раз, создаёшь любое количество экземпляров.

🏭 Аналогия: класс — это форма для печенья. Описываешь форму один раз — делаешь сколько угодно одинаково-фигурных печенек с разной начинкой (данными).

📐 Синтаксис класса: class, constructor, new

Вот как выглядит базовый класс:

class User {
  constructor(name, email) {
    this.name = name;       // this — создаваемый объект
    this.email = email;
    this.balance = 0;       // начальное значение по умолчанию
  }

  greet() {
    console.log(`Привет, меня зовут ${this.name}!`);
  }

  getInfo() {
    return `${this.name} (${this.email}), баланс: ${this.balance} ₽`;
  }
}

// Создаём экземпляры через new
const anna = new User("Анна", "a@mail.ru");
const boris = new User("Борис", "b@mail.ru");

anna.greet();          // Привет, меня зовут Анна!
boris.greet();         // Привет, меня зовут Борис!
console.log(anna.getInfo());  // Анна (a@mail.ru), баланс: 0 ₽

Разберём каждую часть по очереди:

class User { ... }

Объявление класса. User — имя класса в PascalCase (каждое слово с большой буквы). Это договорённость: классы всегда именуются так.

constructor(name, email)

Специальный метод инициализации. Вызывается автоматически при new User(...). Здесь задают начальное состояние объекта через this.свойство = значение.

new User("Анна", "a@m.ru")

Создание нового экземпляра класса. new — обязательное слово. Без него User("Анна") вызовет ошибку или вернёт не то.

🔤 Именование: PascalCase для классов

Это важная договорённость, которую нужно соблюдать. В JavaScript:

  • Переменные и функции — camelCase: userName, getBalance, isActive
  • Классы — PascalCase: User, BankAccount, ShoppingCart, UserProfile
  • Константы — UPPER_SNAKE_CASE: MAX_RETRIES, API_URL

PascalCase сигнализирует: «это класс, используй new». Если видишь new Something() — значит, Something — класс.

🏦 Практика: класс BankAccount

Давайте напишем полноценный класс банковского счёта — он отлично иллюстрирует все концепции:

class BankAccount {
  constructor(owner, initialBalance = 0) {
    this.owner = owner;
    this.balance = initialBalance;
    this.transactions = [];            // история операций
  }

  deposit(amount) {
    if (amount <= 0) return console.log("Сумма должна быть больше 0");
    this.balance += amount;
    this.transactions.push({ type: "deposit", amount });
    console.log(`Пополнено +${amount} ₽. Баланс: ${this.balance} ₽`);
  }

  withdraw(amount) {
    if (amount <= 0) return console.log("Сумма должна быть больше 0");
    if (amount > this.balance) return console.log("Недостаточно средств!");
    this.balance -= amount;
    this.transactions.push({ type: "withdraw", amount });
    console.log(`Снято -${amount} ₽. Баланс: ${this.balance} ₽`);
  }

  printStatement() {
    console.log(`=== Выписка: ${this.owner} ===`);
    this.transactions.forEach(t => {
      const sign = t.type === "deposit" ? "+" : "-";
      console.log(`  ${sign}${t.amount} ₽`);
    });
    console.log(`Итого: ${this.balance} ₽`);
  }
}

const acc1 = new BankAccount("Иван Петров", 5000);
const acc2 = new BankAccount("Мария Сидорова");  // balance = 0

acc1.deposit(3000);    // Пополнено +3000 ₽. Баланс: 8000 ₽
acc1.withdraw(1500);   // Снято -1500 ₽. Баланс: 6500 ₽
acc1.withdraw(10000);  // Недостаточно средств!
acc1.printStatement(); // Выписка с историей

// Объекты независимы — у каждого своё состояние
console.log(acc2.balance); // 0

Обратите внимание: acc1 и acc2 — это два независимых объекта. У каждого свои balance и transactions. Изменение одного не влияет на другой. Это одно из главных преимуществ классов.

🔵 Статические методы: принадлежат классу, не экземпляру

До сих пор все методы вызывались через экземпляр: acc1.deposit(). Но иногда нужен метод, который принадлежит самому классу, а не конкретному объекту. Для этого есть ключевое слово static.

🔵 Статический метод вызывается через сам класс: ClassName.method(), а не через экземпляр. Внутри него this — это сам класс, а не объект.

class MathHelper {
  static add(a, b) {
    return a + b;
  }

  static multiply(a, b) {
    return a * b;
  }

  static square(n) {
    return n * n;
  }
}

// Вызываем через класс, без new!
console.log(MathHelper.add(5, 3));      // 8
console.log(MathHelper.multiply(4, 6)); // 24
console.log(MathHelper.square(7));      // 49

// Вот пример из реального JS:
// Math.abs(), Math.max(), Math.random() — всё это статические методы класса Math!

Статические методы часто используют для:

  • Утилитных функций — как Math.abs(), Math.max()
  • Фабричных методов — альтернативных способов создания объектов
  • Валидации — проверка данных перед созданием экземпляра
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // Статический метод — фабрика
  static createFromString(str) {
    const [name, email] = str.split("|");
    return new User(name.trim(), email.trim());
  }

  // Статический метод — валидация
  static isValidEmail(email) {
    return email.includes("@") && email.includes(".");
  }
}

const u = User.createFromString("Алексей | alex@mail.ru");
console.log(u.name);  // Алексей

console.log(User.isValidEmail("test@mail.ru")); // true
console.log(User.isValidEmail("нет_собаки"));   // false

🔒 Приватные поля: # (ES2022)

Раньше мы использовали _balance как условно-приватное поле. Но это лишь договорённость — технически ничто не мешало написать acc._balance = 1000000. В ES2022 появились настоящие приватные поля через символ #:

class BankAccount {
  #balance = 0;   // настоящее приватное поле — с # !

  constructor(owner, initial = 0) {
    this.owner = owner;
    this.#balance = initial;
  }

  deposit(amount) {
    if (amount > 0) this.#balance += amount;
  }

  get balance() {
    return this.#balance;   // читать можно через геттер
  }
}

const acc = new BankAccount("Иван", 1000);
console.log(acc.balance);   // 1000 (через геттер — OK)

// Попытка залезть снаружи:
console.log(acc.#balance);  // SyntaxError — реально запрещено!
acc.#balance = 9999999;     // SyntaxError — защита работает!

Приватные поля (#field) — это не просто договорённость, а настоящая защита на уровне языка. Снаружи класса к ним невозможно обратиться — синтаксическая ошибка. Внутри класса — пожалуйста.

⚠️ Поддержка # появилась в Node.js 12+ и современных браузерах. Если работаете с устаревшим окружением — используйте _-конвенцию.

🍭 class — это синтаксический сахар

Важное понимание: class в JavaScript — это не отдельный механизм, а красивая обёртка над старым механизмом прототипов. Движок JavaScript при виде class User { ... } «под капотом» делает то, что раньше писали вручную — через функции-конструкторы и prototype.

Вот доказательство — оба способа создают одно и то же:

// Старый способ (до ES6, 2015):
function User(name) {
  this.name = name;
}
User.prototype.greet = function() {
  console.log("Привет, " + this.name);
};

// Новый способ (ES6+):
class User {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log("Привет, " + this.name);
  }
}

// Оба варианта работают одинаково!

Прототипы мы разберём в уроке 8.5. Сейчас важно знать: class — это просто удобный синтаксис, который делает код понятнее. Используйте его — это современный стандарт.

✅ Итоги урока

  • class — шаблон для создания множества однотипных объектов. Синтаксический сахар над прототипами
  • constructor — специальный метод инициализации, вызывается автоматически при new
  • new ClassName(args) — создание экземпляра. Каждый экземпляр независим, имеет свои данные
  • Имена классов — в PascalCase: User, BankAccount, ShoppingCart
  • static — метод принадлежит классу, не экземпляру. Вызывается как ClassName.method()
  • #field — настоящее приватное поле (ES2022). Недоступно снаружи класса на уровне синтаксиса
  • class — это синтаксический сахар. Под капотом JavaScript использует прототипы

В следующем уроке — наследование: как один класс берёт всё лучшее от другого через extends и super. 🚀

Классы в JavaScript

7 вопросов

Класс Book

// Создайте класс Book с: // - конструктором, принимающим title, author, year // - методом getInfo(), возвращающим строку // "title — author (year)" // Ваш код здесь

Класс Counter со статическим методом

// Создайте класс Counter: // - конструктор устанавливает начальное значение 0 // - метод increment() увеличивает значение на 1 // - метод getValue() возвращает текущее значение // - статический метод createMultiple(n) возвращает // массив из n новых экземпляров Counter // Ваш код здесь