В об’єктно-орієнтованому програмуванні, клас – це спеціальна конструкція, яка використовується для групування пов’язаних змінних та функцій. При цьому, згідно з термінологією ООП, глобальні змінні класу (члени-змінні) називаються полями даних (також властивостями або атрибутами), а члени-функції називають методами класу.
На практиці ми часто повинні створювати багато об’єктів одного й того ж виду, наприклад користувачів, або товари чи що завгодно.
Як ми вже знаємо з розділу Конструктори, оператор "new", new function може допомогти з цим.
Але в сучасному JavaScript існує більш просунута конструкція “клас”, яка вводить нові чудові функції, які корисні для об’єктно-орієнтованого програмування.
Синтаксис “class”
Базовий синтаксис:
class MyClass {
// методи класу
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
Потім використовуйте new MyClass(), щоб створити новий об’єкт з усіма перерахованими методами.
Метод constructor() викликається автоматично за допомогою new, в ньому ми можемо ініціалізувати об’єкт.
Наприклад:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Використання:
let user = new User("Іван");
user.sayHi();
Коли new User("Іван") викликається:
- Створюється новий об’єкт.
constructorзапускається з даним аргументом і присвоює його вthis.name.
…Потім ми можемо викликати методи об’єкту, такі як user.sayHi().
Часта помилка розробників-початківців полягає в тому, що ставляться коми між методами класу, що призводить до синтаксичної помилки.
Синтаксис класів не слід плутати з літералами об’єктів. У межах класу не треба ставити кому.
Що таке клас?
Отже, чим насправді є class? Він не є цілком новою сутністю мови програмування, як можна подумати.
Тож, розвіймо магію і подивімося, чим в дійсності є клас. Це допоможе в розумінні багатьох складних аспектів.
У JavaScript клас є своєрідною функцією.
Погляньте на це:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// доказ: User -- це функція
alert(typeof User); // function
Конструкція class User {...} в дійсності робить наступне:
- Створює функцію, що називається
Userта стає результатом оголошення класу. Код цієї функції береться з методуconstructor(вважається порожнім, якщо ми не написали такий метод). - Записує методи класу, такі як
sayHi, доUser.prototype.
Після того, як new User створився, коли ми викликаємо його метод, він береться з прототипу, як описано в розділі F.prototype. Таким чином, об’єкт має доступ до методів класу.
Ми можемо проілюструвати результат оголошення class User наступним чином:
Ось код, щоб проаналізувати це:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// клас -- це функція
alert(typeof User); // function
// ...або, точніше, метод конструктора
alert(User === User.prototype.constructor); // true
// Методи знаходяться в User.prototype, наприклад:
alert(User.prototype.sayHi); // код sayHi методу
// у прототипі існує рівно два методи
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
Не просто синтаксичний цукор
Іноді люди кажуть, що class – це “синтаксичний цукор” (синтаксис, який призначений для того, щоб полегшити читання, але не додає нічого нового), тому що фактично ми могли б оголосити те саме без використання ключового слова class:
// перепишемо клас User в чистих функціях
// 1. Створимо функцію-конструктор
function User(name) {
this.name = name;
}
// прототип функції має властивість "constructor" за замовчуванням,
// тому нам не потрібно її створювати
// 2. Додамо метод до прототипу
User.prototype.sayHi = function() {
alert(this.name);
};
// Використання:
let user = new User("Іван");
user.sayHi();
Результат цього оголошення дуже схожий. Отже, дійсно існують причини, чому class можна вважати синтаксичним цукром для того, щоб визначити конструктор разом із його методами прототипу.
Проте, існують важливі відмінності.
-
По-перше, функція, що створена за допомогою
class, позначена спеціальною внутрішньою властивістю[[IsClassConstructor]]: true. Так що це не зовсім те саме, що створити її вручну.Рушій перевіряє цю властивість у різних місцях. Наприклад, на відміну від звичайної функції, її треба викликати з
new:class User { constructor() {} } alert(typeof User); // function User(); // Помилка: Конструктор класу User неможливо викликати без 'new'Також, рядкове представлення конструктора класу у більшості рушіїв JavaScript починається з “class…”
class User { constructor() {} } alert(User); // class User { ... }Є й інші відмінності, ми побачимо їх найближчим часом.
-
Методи класу неперелічувані. Оголошення класу встановлює прапор
enumerableуfalseдля всіх методів в"prototype".Це добре тому, що коли ми перебираємо об’єкт за допомогою
for..in, ми зазвичай не хочемо обробляти методи класу. -
Клас завжди
use strict. Весь код всередині конструкції класу автоматично знаходиться в суворому режимі.
Крім того, синтаксис class приносить багато інших особливостей, які ми дослідимо пізніше.
Class Expression
Так само, як функції, класи можуть бути визначені всередині іншого виразу, передані, повернуті, присвоєні тощо.
Ось приклад class expression:
let User = class {
sayHi() {
alert("Привіт");
}
};
Подібно до Named Function Expression, Class Expression може мати назву.
Якщо Class Expression має назву, то її видно лише всередині класу:
// "Named Class Expression"
// (немає такого терміну у специфікації, але це схоже на Named Function Expression)
let User = class MyClass {
sayHi() {
alert(MyClass); // назву MyClass видно тільки всередині класу
}
};
new User().sayHi(); // працює, показує визначення MyClass
alert(MyClass); // помилка, назву MyClass не видно за межами класу
Ми можемо навіть зробити класи динамічними “на вимогу”, наприклад:
function makeClass(phrase) {
// оголошуємо клас і повертаємо його
return class {
sayHi() {
alert(phrase);
}
};
}
// Створюємо новий клас
let User = makeClass("Привіт");
new User().sayHi(); // Привіт
Геттери/сеттери
Подібно до літералів об’єктів, класи можуть включати геттери/сеттери, обчислені атрибути тощо.
Ось приклад для user.name, що реалізований за допомогою get/set:
class User {
constructor(name) {
// викликає сеттер
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Ім’я занадто коротке.");
return;
}
this._name = value;
}
}
let user = new User("Іван");
alert(user.name); // Іван
user = new User(""); // Ім’я занадто коротке.
Технічно, таке оголошення класу працює шляхом створення геттерів та сеттерів в User.prototype.
Обчислені назви […]
Ось приклад з обчисленою назвою методу за допомогою використання дужок [...]:
class User {
['say' + 'Hi']() {
alert("Привіт");
}
}
new User().sayHi();
Такі особливості легко запам’ятати, оскільки вони нагадують літерали об’єктів.
Поля класу
Поля класу – це недавнє доповнення до мови.
Раніше наші класи мали лише методи.
“Поля класу” – це синтаксис, який дозволяє додавати будь-які властивості.
Наприклад, додаймо властивість name до class User:
class User {
name = "Іван";
sayHi() {
alert(`Привіт, ${this.name}!`);
}
}
new User().sayHi(); // Привіт, Іван!
Отже, ми просто пишемо <property name> = <value> в оголошенні, і все.
Важливою відмінністю полів класу є те, що вони задаються в окремих об’єктах, а не в User.prototype: