Класи

Класи — це зразки, за якими створюються нові об'єкти. Вони інкапсулюють дані й код, який працює з цими даними. Класи в JS побудовані на прототипах, хоча й містять певні унікальні синтаксичні та семантичні особливості.

Більше прикладів та пояснень – у посібнику Застосування класів.

Опис

Означення класів

Насправді класи — це "особливі функції". Тож так само, як функції оголошуються за допомогою виразів функцій та оголошень функцій, клас можна означити в один із двох способів: вираз класу або оголошення класу.

// Оголошення
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

// Вираз; клас є анонімним, однак присвоюється змінній
const Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// Вираз; цей клас має власну назву
const Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Подібно до виразів функцій, вирази класів можуть бути анонімними, а ще можуть мати назву, котра відрізняється від назви змінної, котрій такий клас присвоюється. Проте на відміну від оголошень функцій, оголошення класів мають такі ж обмеження темпоральної мертвої зони, що й let і const, і поводяться так, ніби не підіймаються.

Тіло класу

Тіло класу – його частина, що лежить всередині фігурних дужок {}. Саме в ньому оголошуються члени класу, як то методи чи конструктор.

Тіло класу виконується в суворому режимі, навіть коли не містить директиви "use strict".

Елемент класу характеризується трьома аспектами:

  • Ґатунок: гетер, сетер, метод або поле
  • Місце: статичний або на примірнику
  • Видимість: публічна або приватна

Вкупі це дає до 16 можливих комбінацій. Аби більш логічно розділити довідку й уникнути перекриття вмісту, докладне знайомство з різними елементами винесено на окремі сторінки:

Означення методів

Публічний метод примірника

гетер

Публічний гетер примірника

сетер

Публічний сетер примірника

Публічні поля класу

Публічне поле примірника

static

Публічний статичний метод, гетер, сетер чи поле

Приватні властивості

Усе, що приватно

Примітка: Приватні властивості мають таке обмеження, що всі імена властивостей, оголошені в одному класі, повинні бути неповторними. Інші, публічні властивості – не мають такого обмеження: можна мати декілька публічних властивостей з одним іменем, і остання серед них замінить всі попередні. Так само це працює в об'єктних ініціалізаторах.

На додачу до цього, є два особливі записи елементів класу: constructor і блоки статичної ініціалізації, котрі мають власні довідкові матеріали.

Конструктор

Метод constructor – це спеціальний метод для створення та ініціалізації об'єктів, створених за допомогою класу. В класі може бути лише один спеціальний метод з назвою "constructor" – викидається SyntaxError, якщо клас містить більш ніж один метод constructor.

Конструктор може застосовувати ключове слово super для виклику конструктора надкласу.

Всередині конструктора можна створювати властивості примірника:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Інший варіант: якщо значення властивостей примірника не залежать від аргументів конструктора, то їх можна означувати як поля класу.

Блоки статичної ініціалізації

Блоки статичної ініціалізації дають змогу гнучко ініціалізувати статичні властивості, в тому числі виконувати інструкції під час ініціалізації, надаючи доступ до приватних областей.

Можна оголошувати декілька статичних блоків і перемежовувати їх з оголошеннями статичних полів і методів (всі статичні елементи опрацьовуються в порядку їх оголошення).

Методи

Методи означаються на прототипі всіх примірників класу, їх поділяють всі примірники. Методи можуть бути простими функціями, асинхронними функціями, генераторними функціями й асинхронними генераторними функціями. Більше про це – в означенні методів.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Гетер
  get area() {
    return this.calcArea();
  }
  // Метод
  calcArea() {
    return this.height * this.width;
  }
  *getSides() {
    yield this.height;
    yield this.width;
    yield this.height;
    yield this.width;
  }
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]

Статичні методи та поля

Ключове слово static оголошує статичний метод чи поле класу. Статичні властивості (поля та методи) означаються на самому класі, а не кожному примірнику. Статичні методи часто використовуються для створення допоміжних функцій для застосунку, а статичні поля корисні для кешів, фіксованої конфігурації чи будь-яких інших даних, які не потрібно дублювати на кожний примірник.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static displayName = "Point";
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.hypot(dx, dy);
  }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined

console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755

Оголошення полів

За допомогою синтаксису оголошення полів класу приклад constructor може бути переписаний так:

class Rectangle {
  height = 0;
  width;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Поля класів подібні до властивостей об'єктів, а не змінних, тож для їх оголошення не потрібні ключові слова штибу const. Приватні властивості у JavaScript використовують особливий синтаксис ідентифікаторів, тому public і private також не повинні використовуватися.

Як видно вище, поля можуть бути оголошені як з усталеним значенням, так і без нього. Поля без усталених значень усталено мають значення undefined. Коли оголошувати поля заздалегідь, то вони виходять більш самодокументованими, а іще – такі поля присутні завжди, що допомагає оптимізації.

Більше інформації – на сторінці публічних полів класів.

Приватні властивості

Вищезазначений опис класу можна переробити у такий спосіб, застосувавши приватні поля:

class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
}

Звертання до приватних полів класу зовні є помилкою, їх можна читати й записувати лише зсередини тіла класу. Шляхом оголошення полів класу, невидимих ззовні, можна гарантувати, що користувачі класу не матимуть змоги залежати від внутрішніх деталей реалізації, які можуть змінюватися від версії до версії.

Приватні поля можна оголошувати лише заздалегідь, під час опису полів. Їх не можна створювати пізніше шляхом присвоєння їм значень, так, як це можна робити зі звичайними властивостями.

Додаткову інформацію можна знайти в розділі приватних властивостей.

Успадкування

Ключове слово extends використовується в оголошеннях класів чи класових виразах для створення класу, який є нащадком іншого конструктора (класу або функції).

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} галасує.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // виклик конструктора суперкласу із передачею йому параметру імені
  }

  speak() {
    console.log(`${this.name} гавкає.`);
  }
}

const d = new Dog("Сірко");
d.speak(); // Сірко гавкає.

Якщо в підкласі є конструктор, то такий конструктор мусить викликати super(), перш ніж використовувати this. Також ключове слово super може застосовуватись для виклику відповідних методів надкласу.

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} галасує.`);
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(`${this.name} гарчить.`);
  }
}

const l = new Lion("Пушок");
l.speak();
// Пушок галасує.
// Пушок гарчить.

Порядок виконання

Коли виконується оголошення class або вираз class, то різні його компоненти виконуються в такому порядку:

  1. Першим виконується положення extends, якщо воно є. Воно повинно обчислюватись до дійсної функції-конструктора або null, інакше – буде викинуто TypeError.
  2. Видобувається метод constructor; він замінюється усталеною реалізацією constructor, якщо відсутній. Проте оскільки визначення constructor є лише визначенням методу, цей крок не відстежується.
  3. У порядку оголошення обчислюються ключі властивостей елементів класу. Якщо ключ властивості є обчислюваним, то його вираз обчислюється, і this при цьому дорівнює значенню this навколо класу (а не самому класові). Значення жодної властивості поки не обчислюється.
  4. У порядку оголошення встановлюються методи та аксесори. Методи та аксесори примірника встановлюються на властивості prototype поточного класу, а статичні методи та аксесори – на самому класі. Приватні методи та аксесори примірника – зберігаються для встановлення безпосередньо на примірнику пізніше. Цей крок не відстежується.
  5. Тепер клас ініціалізується прототипом, заданим extends, та реалізацією, заданою constructor. Для всіх кроків вище, якщо обчислюваний вираз намагається отримати доступ до імені класу, то викидається ReferenceError, оскільки клас тоді ще не ініціалізовано.
  6. У порядку оголошення обчислюються значення елементів класу:
    • Для кожного поля примірника (публічного або приватного) – зберігається його вираз-ініціалізатор. Цей ініціалізатор обчислюється під час створення примірника, на початку конструктора (для базових класів) або безпосередньо перед поверненням виклику super() (для похідних класів).
    • Для кожного статичного поля (публічного або приватного) – його ініціалізатор обчислюється, і при цьому this дорівнює самому класові, а властивість створюється на класі.
    • Обчислюються блоки статичної ініціалізації, при чому this дорівнює самому класові.
  7. На цьому етапі клас є цілком ініціалізованим і може використовуватися як функція-конструктор.

Про те, як створюються примірники, дивіться у довідці про constructor.

Приклади

Зв'язування this із примірником і статичні методи

Коли статичний метод або метод примірника викликається без значення this, наприклад, шляхом присвоєння методу змінній, а потім виклику його через змінну, то всередині такого методу this матиме значення undefined. Така логіка діє навіть тоді, коли не задана директива "use strict", адже код всередині тіла class завжди виконується в суворому режимі.

class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}
const obj = new Animal();
obj.speak(); // об'єкт Animal
const speak = obj.speak;
speak(); // undefined
Animal.eat(); // клас Animal
const eat = Animal.eat;
eat(); // undefined

Коли переписати приклад вище за допомогою традиційного синтаксису на основі функцій, у несуворому режимі, то виклики методів з this будуть автоматично прив'язані до globalThis. У суворому режимі значенням this залишиться undefined.

function Animal() {}
Animal.prototype.speak = function () {
  return this;
};
Animal.eat = function () {
  return this;
};
const obj = new Animal();
const speak = obj.speak;
speak(); // глобальний об'єкт (у несуворому режимі)
const eat = Animal.eat;
eat(); // глобальний об'єкт (у несуворому режимі)

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
classes
Chrome Full support 49
Edge Full support 13
Firefox Full support 45
Internet Explorer No support No
Opera Full support 36
Safari Full support 9
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 45
Opera Android Full support 36
Safari on iOS Full support 9
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
constructor Chrome Full support 49
Edge Full support 13
Firefox Full support 45
Internet Explorer No support No
Opera Full support 36
Safari Full support 9
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 45
Opera Android Full support 36
Safari on iOS Full support 9
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
extends Chrome Full support 49
Edge Full support 13
Firefox Full support 45
Internet Explorer No support No
Opera Full support 36
Safari Full support 9
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 45
Opera Android Full support 36
Safari on iOS Full support 9
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Private class fields Chrome Full support 74
Edge Full support 79
Firefox Full support 90
Internet Explorer No support No
Opera Full support 62
Safari Full support 14.1
WebView Android Full support 74
Chrome Android Full support 74
Firefox for Android Full support 90
Opera Android Full support 53
Safari on iOS Full support 14.5
Samsung Internet Full support 11.0
Deno Full support 1.0
Node.js Full support 12.0.0
Private class fields \'in\' Chrome Full support 91
Edge Full support 91
Firefox Full support 90
Internet Explorer No support No
Opera Full support 77
Safari Full support 15
WebView Android Full support 91
Chrome Android Full support 91
Firefox for Android Full support 90
Opera Android Full support 64
Safari on iOS Full support 15
Samsung Internet Full support 16.0
Deno Full support 1.9
Node.js Full support 16.4.0
Private class methods Chrome Full support 84
Edge Full support 84
Firefox Full support 90
Internet Explorer No support No
Opera Full support 70
Safari Full support 15
WebView Android Full support 84
Chrome Android Full support 84
Firefox for Android Full support 90
Opera Android Full support 60
Safari on iOS Full support 15
Samsung Internet Full support 14.0
Deno Full support 1.0
Node.js Full support 14.6.0
Public class fields Chrome Full support 72
Edge Full support 79
Firefox Full support 69
Internet Explorer No support No
Opera Full support 60
Safari Full support 14.1
WebView Android Full support 72
Chrome Android Full support 72
Firefox for Android Full support 79
Opera Android Full support 51
Safari on iOS Full support 14.5
Samsung Internet Full support 11.0
Deno Full support 1.0
Node.js Full support 12.0.0
static Chrome Full support 49
Edge Full support 13
Firefox Full support 45
Internet Explorer No support No
Opera Full support 36
Safari Full support 9
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 45
Opera Android Full support 36
Safari on iOS Full support 9
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Static class fields Chrome Full support 72
Edge Full support 79
Firefox Full support 75
Internet Explorer No support No
Opera Full support 60
Safari Full support 14.1
WebView Android Full support 72
Chrome Android Full support 72
Firefox for Android Full support 79
Opera Android Full support 51
Safari on iOS Full support 14.5
Samsung Internet Full support 11.0
Deno Full support 1.0
Node.js Full support 12.0.0
Class static initialization blocks Chrome Full support 94
Edge Full support 94
Firefox Full support 93
Internet Explorer No support No
Opera Full support 80
Safari No support No
WebView Android Full support 94
Chrome Android Full support 94
Firefox for Android Full support 93
Opera Android Full support 66
Safari on iOS No support No
Samsung Internet Full support 17.0
Deno Full support 1.14
Node.js Full support 16.11.0

Дивіться також