Класи
Класи — це зразки, за якими створюються нові об'єкти. Вони інкапсулюють дані й код, який працює з цими даними. Класи в 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
, то різні його компоненти виконуються в такому порядку:
- Першим виконується положення
extends
, якщо воно є. Воно повинно обчислюватись до дійсної функції-конструктора абоnull
, інакше – буде викинутоTypeError
. - Видобувається метод
constructor
; він замінюється усталеною реалізацієюconstructor
, якщо відсутній. Проте оскільки визначенняconstructor
є лише визначенням методу, цей крок не відстежується. - У порядку оголошення обчислюються ключі властивостей елементів класу. Якщо ключ властивості є обчислюваним, то його вираз обчислюється, і
this
при цьому дорівнює значеннюthis
навколо класу (а не самому класові). Значення жодної властивості поки не обчислюється. - У порядку оголошення встановлюються методи та аксесори. Методи та аксесори примірника встановлюються на властивості
prototype
поточного класу, а статичні методи та аксесори – на самому класі. Приватні методи та аксесори примірника – зберігаються для встановлення безпосередньо на примірнику пізніше. Цей крок не відстежується. - Тепер клас ініціалізується прототипом, заданим
extends
, та реалізацією, заданоюconstructor
. Для всіх кроків вище, якщо обчислюваний вираз намагається отримати доступ до імені класу, то викидаєтьсяReferenceError
, оскільки клас тоді ще не ініціалізовано. - У порядку оголошення обчислюються значення елементів класу:
- Для кожного поля примірника (публічного або приватного) – зберігається його вираз-ініціалізатор. Цей ініціалізатор обчислюється під час створення примірника, на початку конструктора (для базових класів) або безпосередньо перед поверненням виклику
super()
(для похідних класів). - Для кожного статичного поля (публічного або приватного) – його ініціалізатор обчислюється, і при цьому
this
дорівнює самому класові, а властивість створюється на класі. - Обчислюються блоки статичної ініціалізації, при чому
this
дорівнює самому класові.
- Для кожного поля примірника (публічного або приватного) – зберігається його вираз-ініціалізатор. Цей ініціалізатор обчислюється під час створення примірника, на початку конструктора (для базових класів) або безпосередньо перед поверненням виклику
- На цьому етапі клас є цілком ініціалізованим і може використовуватися як функція-конструктор.
Про те, як створюються примірники, дивіться у довідці про 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 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
classes
|
Chrome Full support 49 | Edge Full support 13 | Firefox Full support 45 | Internet Explorer No support Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | 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 Ні | Opera Full support 80 | Safari No support Ні | 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 Ні | Samsung Internet Full support 17.0 | Deno Full support 1.14 | Node.js Full support 16.11.0 |
Дивіться також
- Посібник Застосування класів
class
- Вираз
class
- Функції
- Заглиблення в ES6: Класи на hacks.mozilla.org (2015)