Клас як вираз const A = class {}
Так, клас у JavaScript, це вираз, тому const A = class {} абсолютно легальний і створює такий самий клас, як звичайна декларація. Відрізняється лише те, як клас потрапляє в область видимості, і чи має він власне ім'я, видиме ззовні.
Теорія
TL;DR
- Клас можна оголосити декларацією
class User {}або виразомconst User = class {}. - Клас-вираз буває анонімним і іменованим; ім'я іменованого видно лише всередині тіла класу.
- Клас-вираз потрібен для динамічного вибору класу, фабрик класів і локальних класів усередині функції.
- Ні декларація, ні вираз не можна використати до оголошення: обидва потрапляють у temporal dead zone.
- Клас можна створити й використати одразу, inline:
new (class { ... })().
Швидкий приклад
// Class declaration
class UserA {
greet() { console.log("Hello!"); }
}
// Class expression
const UserB = class {
greet() { console.log("Hello!"); }
};
new UserA().greet(); // Hello!
new UserB().greet(); // Hello!Обидва варіанти створюють клас, різниця лише в тому, де і як він оголошений.
Анонімний і іменований вираз
Клас-вираз може бути анонімним або іменованим.
Анонімний:
const User = class {
greet() {
console.log("Hello!");
}
};Іменований:
const User = class MyUser {
greet() {
console.log(MyUser.name); // "MyUser"
}
};
const u = new User();
u.greet(); // MyUserІм'я
MyUserвидно тільки всередині самого класу, ззовні доступна лише зміннаUser.
console.log(typeof MyUser); // ReferenceErrorЦе схоже на іменовані функціональні вирази: внутрішнє ім'я зручне для рекурсивних звернень і для читабельних стек-трейсів, але воно не створює зовнішньої змінної.
Де це корисно
1. Коли клас треба створити динамічно. Наприклад, залежно від умови:
const Animal = condition
? class Dog {
bark() { console.log("Woof!"); }
}
: class Cat {
meow() { console.log("Meow!"); }
};
const pet = new Animal();2. Коли клас треба повернути з функції (фабрика класів).
function createModel(name) {
return class {
constructor(id) {
this.id = id;
this.modelName = name;
}
};
}
const UserModel = createModel("User");
const user = new UserModel(1);
console.log(user.modelName); // "User"Дуже зручно для динамічного створення моделей, сутностей, адаптерів тощо. Той самий прийом лежить в основі міксинів: функція приймає базовий клас і повертає
class extends Base { ... }.
3. Коли клас має бути локальним, усередині функції.
function makeCounter() {
return class {
#count = 0;
inc() { this.#count++; return this.#count; }
};
}
const Counter = makeCounter();
const c = new Counter();
console.log(c.inc()); // 1Клас живе тільки всередині функції й не засмічує глобальну область видимості.
Поведінка hoisting
Клас-декларація не піднімається так, як функція: ім'я класу резервується, але до рядка оголошення воно перебуває в temporal dead zone. Клас-вираз поводиться як будь-яка змінна const, тобто доступний тільки після оголошення.
new User(); // ReferenceError: Cannot access 'User' before initialization
const User = class {};Тобто в питанні hoisting обидві форми на практиці однакові: використати клас до його оголошення не можна.
Клас як значення inline
Оскільки клас, це вираз, його можна створити й одразу застосувати, не даючи імені:
console.log(
new (class {
sayHi() {
console.log("Hello!");
}
})()
); // an instance of a temporary classТакий прийом часто застосовують у тестах і демо, щоб не заводити окреме ім'я класу.
Підсумкова таблиця
| Тип | Приклад | Де доступне ім'я | Піднімається (hoisting)? |
|---|---|---|---|
| Class Declaration | class User {} | Скрізь після оголошення | Ні |
| Anonymous Class Expression | const A = class {} | Імені немає | Ні |
| Named Class Expression | const A = class MyA {} | Тільки всередині класу | Ні |
Висновок: клас можна використовувати як вираз, це зручно для динамічних, локальних і тимчасових класів, а ім'я класу при такому способі може бути локальним або взагалі відсутнім.
Типові помилки
- Думати, що ім'я іменованого класу-виразу видно ззовні.
const A = class MyA {}не створює змінноїMyAу зовнішній області видимості. - Використовувати клас до рядка оголошення. Це
ReferenceErrorчерез temporal dead zone, і для декларації теж. - Забувати крапку з комою після виразу.
const A = class {}закінчується виразом, тому пропуск;може призвести до несподіваного продовження рядка. - Плутати
A.nameдля анонімного класу. Уconst A = class {}рушій виведе ім'я зі змінної, тожA.nameдасть"A", а не порожній рядок. - Створювати клас у гарячому циклі. Фабрика класів на кожен виклик створює новий клас з новим прототипом, тому екземпляри від різних викликів не проходять
instanceofодин одного. - Очікувати, що клас-вираз обходить strict mode. Тіло класу завжди strict, незалежно від форми оголошення.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.