Клас проти функції-конструктора
Клас це синтаксичний цукор над прототипами, але не тільки цукор: він додає кілька жорстких правил поведінки, яких немає у функції-конструктора. Обидва механізми зрештою створюють об'єкт із прототипом, проте клас забороняє виклик без new, вмикає strict mode, ховає методи від перебору і не піднімається вгору по області видимості.
Теорія
TL;DR
- Клас неможливо викликати без
new: цеTypeError. Функцію-конструктор можна, і вона тихо зіпсує глобальний об'єкт або впаде. - Тіло класу завжди виконується у strict mode, навіть без
'use strict'. - Методи класу не перелічувані, тому не з'являються у
for...inтаObject.keys(Prototype). - Класи не мають hoisting: звернення до класу до оголошення дає
ReferenceError. - Успадкування класів це
extendsіsuper, у функцій це ручніObject.createтаcall. - Приватні поля
#fieldіснують лише у класах.
Швидкий приклад
function UserFn(name) {
this.name = name;
}
UserFn("Alice"); // працює, але пише у глобальний об'єкт або падає у strict mode
class UserClass {
constructor(name) { this.name = name; }
}
UserClass("Alice"); // TypeError: Class constructor UserClass cannot be invoked without 'new'Синтаксис і читабельність
Функція-конструктор:
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
console.log(`Hi, ${this.name}`);
};
const user = new User("Alice");
user.greet();Клас:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi, ${this.name}`);
}
}
const user = new User("Alice");
user.greet();Клас виглядає чистіше й декларативніше, особливо коли з'являється успадкування. Функція-конструктор змушує вручну прописувати prototype, Object.create() і подібне.
Виклик без new і strict mode
Функцію-конструктор можна випадково викликати без new, і це призведе до помилок: this стане undefined у strict-режимі або прив'яжеться до глобального об'єкта.
function User(name) {
this.name = name;
}
const u = User("Alice"); // помилка або створить глобальну властивість window.nameКлас викликати без new не можна, це одразу виняток:
class User {
constructor(name) {
this.name = name;
}
}
const u = User("Alice"); // TypeError: Class constructor User cannot be invoked without 'new'Це робить класи безпечнішими. Додатково все тіло класу автоматично працює у strict mode, навіть якщо 'use strict' ніде не написано. Функція-конструктор такої гарантії не дає: без явного 'use strict' вона виконується у звичайному режимі.
Перелічуваність методів, поля класу і hoisting
Методи, визначені всередині class {}, не перелічувані, тому не з'являються під час перебору for...in, а методи на prototype функції-конструктора перелічувані:
class A {
m() {}
}
console.log(Object.keys(A.prototype)); // []
function B() {}
B.prototype.m = function() {};
console.log(Object.keys(B.prototype)); // ["m"]Це робить класи «чистішими» для ітерації та серіалізації.
У тілі класу не можна було оголошувати методи як звичайні функції-вирази:
class A {
m = function() {}; // SyntaxError у старих версіях рушіїв
}Для цього потрібен синтаксис полів класу або стрілкові функції, які стандартизували пізніше (ES2022 і новіші).
Нарешті, класи не мають hoisting. Функції-конструктори можна викликати до їхнього оголошення:
const a = new User();
function User() {}Класи так не працюють:
const a = new User(); // ReferenceError
class User {}Це запобігає неочікуваним помилкам під час виконання.
Успадкування
З функціями-конструкторами доводиться писати багато зайвого:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;А з класами:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}Коротко і зрозуміло. До того ж клас дає super для звернення до реалізації батька, тоді як у функцій доводиться вручну кликати Animal.prototype.speak.call(this).
Підсумкова таблиця
| Відмінність | Клас | Функція-конструктор |
|---|---|---|
Виклик без new | Помилка TypeError | Працює, але призводить до багів |
| Strict mode | Завжди увімкнений | Немає за замовчуванням |
| Hoisting | Немає | Є |
| Методи перелічувані | Ні | Так |
| Синтаксис успадкування | Простий (extends, super) | Складний (Object.create, call) |
| Приватні поля | Є (#field) | Немає |
| Читабельність | Висока | Низька |
Типові помилки
- Казати на співбесіді, що «клас це просто цукор», і зупинятися на цьому. Поведінкові відмінності (виклик без
new, strict mode, hoisting, перелічуваність) якраз і перевіряють. - Плутати відсутність hoisting із повною відсутністю підйому: оголошення класу піднімається, але лежить у temporal dead zone, тому й виникає
ReferenceError, а неis not defined. - Забувати
Dog.prototype.constructor = Dogпід час ручного успадкування і потім дивуватися, чомуinstance.constructorвказує наAnimal. - Вважати
_fieldу функції-конструкторі приватним. Справжня приватність є лише у полях класу з#. - Передавати метод класу як колбек і втрачати
this: ні клас, ні функція-конструктор самі контекст не прив'язують.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.