Skip to main content

Класи в JavaScript

Клас у JavaScript це синтаксичний цукор над функцією-конструктором і прототипами. Ззовні це декларативний опис типу об'єкта, а під капотом усе працює так само, як і раніше: методи лягають у prototype, а new створює новий об'єкт і прив'язує до нього this. Виглядає й читається такий код значно простіше.

Теорія

TL;DR

  • class це синтаксичний цукор над функцією-конструктором і прототипами, а не нова модель успадкування.
  • constructor() викликається при new, а методи з тіла класу потрапляють у prototype і є спільними для всіх екземплярів.
  • extends зв'язує прототипи двох класів, super() викликає конструктор батька.
  • static робить метод або властивість членом самого класу, а не екземпляра.
  • Поля з # приватні й доступні лише всередині тіла класу (ES2022), а get / set керують читанням і записом властивостей.
  • Класи не піднімаються (немає hoisting) як функції, а їхні методи не перелічувані (non-enumerable).

Швидкий приклад

javascript
class User { constructor(name, age) { this.name = name; this.age = age; } sayHi() { console.log(`Привіт, мене звати ${this.name}`); } } const user1 = new User('Alex', 25); user1.sayHi(); // Привіт, мене звати Alex
  • class оголошує клас.
  • constructor() це спеціальний метод, який викликається при new User().
  • this це посилання на об'єкт, що створюється.
  • Методи класу автоматично потрапляють у prototype.

Як це виглядає під капотом

Цей код:

javascript
class User { constructor(name) { this.name = name; } sayHi() { console.log(this.name); } }

еквівалентний старому ES5-коду:

javascript
function User(name) { this.name = name; } User.prototype.sayHi = function() { console.log(this.name); };

Різниця не в моделі, а в деталях поведінки: тіло класу завжди виконується у strict mode, методи класу не перелічувані, тому for...in по екземпляру їх не побачить, а викликати клас без new взагалі не можна.

Складові класу

МожливістьОпис
constructor()Метод, який виконується при створенні об'єкта (new)
методи класуДодаються в prototype і доступні всім екземплярам
extendsУспадкування від іншого класу
super()Виклик конструктора батька
staticСтатичні методи та властивості, належать самому класу
приватні поля (#)Поля, недоступні поза класом
геттери та сеттериДозволяють керувати читанням і записом властивостей

Клас можна оголосити не лише інструкцією, а й виразом (class expression):

javascript
const User = class { sayHi() { console.log('Hi!'); } }; new User().sayHi(); // Hi!

Або дати йому ім'я, видиме тільки всередині самого визначення:

javascript
const User = class MyUser { getClassName() { return MyUser.name; } };

Успадкування: extends і super

javascript
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} видає звук.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // викликає constructor батька this.breed = breed; } speak() { console.log(`${this.name} гавкає!`); } } const rex = new Dog('Rex', 'вівчарка'); rex.speak(); // Rex гавкає!
  • extends зв'язує прототипи класів.
  • super() викликає constructor батька, і в похідному класі його треба викликати до першого звернення до this.
  • Методи можна перевизначати, як у класичному ООП.

static, приватні поля та аксесори

Статичні методи й властивості належать самому класу, а не його екземплярам:

javascript
class MathUtil { static add(a, b) { return a + b; } } console.log(MathUtil.add(2, 3)); // 5

Приватні поля та методи (#) додані в ES2022:

javascript
class Account { #balance = 0; // приватне поле deposit(amount) { this.#balance += amount; } getBalance() { return this.#balance; } } const acc = new Account(); acc.deposit(100); console.log(acc.getBalance()); // 100 console.log(acc.#balance); // помилка: приватна властивість

Поля з # доступні лише всередині класу.

Геттери й сеттери дозволяють керувати тим, як властивість читається і змінюється:

javascript
class User { constructor(name) { this._name = name; } get name() { return this._name.toUpperCase(); } set name(value) { this._name = value.trim(); } } const user = new User(' tim '); console.log(user.name); // TIM user.name = ' Alex '; console.log(user.name); // ALEX

Клас проти функції-конструктора

КритерійФункція-конструкторКлас
СинтаксисСтарий, громіздкийСучасний, декларативний
new обов'язковийТакТак
МетодиЧерез prototypeУсередині тіла класу
Поведінка thisМоже бути непередбачуваноюЗрозуміла, стандартна
Підтримка extendsВручнуВбудована
Приватні поляНемаєЄ (через #)

Коротке резюме синтаксису:

ПоняттяЩо робить
classОголошує клас
constructorМетод, що викликається при new
extendsУспадкування
super()Виклик батьківського конструктора
staticМетоди класу, а не екземпляра
#fieldПриватна властивість
get / setКерування доступом до властивостей

Типові помилки

  1. Думати, що класи піднімаються, як функції. Оголошення класу потрапляє в temporal dead zone, тому звернення до нього вище за рядок з class падає:

    javascript
    const user = new User(); // ReferenceError class User {}
  2. Викликати клас без new. User('Alex') дає TypeError: Class constructor User cannot be invoked without 'new', тоді як стара функція-конструктор у такому виклику мовчки псувала глобальний об'єкт.

  3. Звертатися до this до виклику super(). У похідному класі this ще не існує, доки не відпрацював конструктор батька, тому буде ReferenceError.

  4. Чекати на методи класу у for...in. Вони не перелічувані, на відміну від властивостей, дописаних у прототип вручну.

  5. Передавати метод як колбек і втрачати this. setTimeout(user.sayHi, 0) викличе метод без прив'язки; потрібні user.sayHi.bind(user), стрілкова обгортка або поле класу зі стрілковою функцією.

  6. Плутати #balance з угодою про іменування _balance. Підкреслення це лише домовленість, а поле з # справді недоступне ззовні: звернення до нього поза тілом класу є синтаксичною помилкою.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.