Skip to main content

Клас проти функції-конструктора

Клас це синтаксичний цукор над прототипами, але не тільки цукор: він додає кілька жорстких правил поведінки, яких немає у функції-конструктора. Обидва механізми зрештою створюють об'єкт із прототипом, проте клас забороняє виклик без new, вмикає strict mode, ховає методи від перебору і не піднімається вгору по області видимості.

Теорія

TL;DR

  • Клас неможливо викликати без new: це TypeError. Функцію-конструктор можна, і вона тихо зіпсує глобальний об'єкт або впаде.
  • Тіло класу завжди виконується у strict mode, навіть без 'use strict'.
  • Методи класу не перелічувані, тому не з'являються у for...in та Object.keys(Prototype).
  • Класи не мають hoisting: звернення до класу до оголошення дає ReferenceError.
  • Успадкування класів це extends і super, у функцій це ручні Object.create та call.
  • Приватні поля #field існують лише у класах.

Швидкий приклад

javascript
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'

Синтаксис і читабельність

Функція-конструктор:

javascript
function User(name) { this.name = name; } User.prototype.greet = function () { console.log(`Hi, ${this.name}`); }; const user = new User("Alice"); user.greet();

Клас:

javascript
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-режимі або прив'яжеться до глобального об'єкта.

javascript
function User(name) { this.name = name; } const u = User("Alice"); // помилка або створить глобальну властивість window.name

Клас викликати без new не можна, це одразу виняток:

javascript
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 функції-конструктора перелічувані:

javascript
class A { m() {} } console.log(Object.keys(A.prototype)); // [] function B() {} B.prototype.m = function() {}; console.log(Object.keys(B.prototype)); // ["m"]

Це робить класи «чистішими» для ітерації та серіалізації.

У тілі класу не можна було оголошувати методи як звичайні функції-вирази:

javascript
class A { m = function() {}; // SyntaxError у старих версіях рушіїв }

Для цього потрібен синтаксис полів класу або стрілкові функції, які стандартизували пізніше (ES2022 і новіші).

Нарешті, класи не мають hoisting. Функції-конструктори можна викликати до їхнього оголошення:

javascript
const a = new User(); function User() {}

Класи так не працюють:

javascript
const a = new User(); // ReferenceError class User {}

Це запобігає неочікуваним помилкам під час виконання.

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

З функціями-конструкторами доводиться писати багато зайвого:

javascript
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;

А з класами:

javascript
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: ні клас, ні функція-конструктор самі контекст не прив'язують.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.