Кілька конструкторів у класі
Клас у JavaScript може мати рівно один constructor(). Перевантаження за кількістю чи типами аргументів у мові немає, тому «кілька конструкторів» імітують перевіркою аргументів, об'єктом параметрів, статичними фабриками або успадкуванням.
Теорія
TL;DR
- Два
constructor()в одному класі цеSyntaxError: A class may only have one constructor. - Перевантаження методів, як у Java чи C#, у JavaScript відсутнє в принципі.
- Найпростіша заміна: один конструктор, який сам розбирає свої аргументи.
- Для багатьох параметрів зручніший об'єкт параметрів із деструктуризацією та значеннями за замовчуванням.
- Найчистіший підхід у реальних проєктах: статичні фабричні методи
User.fromName(),User.fromObject(). - Різні набори полів іноді краще виражати різними класами-нащадками.
Швидкий приклад
class User {
constructor(name) {
this.name = name;
}
// SyntaxError: a constructor cannot be declared twice
constructor(name, age) {
this.name = name;
this.age = age;
}
}
SyntaxError: A class may only have one constructor
Чому кількох конструкторів не буває
У JavaScript функція не має сигнатури: кількість і типи аргументів не є частиною її ідентичності. Виклик new User("Tim") і new User("Alex", 25) потрапляють в одну й ту саму функцію, а зайві аргументи просто стають undefined. Через це рушію нема з чого вибирати потрібний конструктор, і повторне оголошення constructor заборонене на рівні синтаксису, ще до виконання коду.
Отже, гнучкість треба будувати всередині єдиного конструктора або поруч із ним.
Варіант 1: перевірка аргументів усередині одного конструктора
class User {
constructor(name, age) {
if (typeof age === "undefined") {
this.name = name;
this.age = "unknown";
} else {
this.name = name;
this.age = age;
}
}
}
const u1 = new User("Tim");
const u2 = new User("Alex", 25);
console.log(u1); // { name: "Tim", age: "unknown" }
console.log(u2); // { name: "Alex", age: 25 }Цей підхід працює як «перевантаження за кількістю аргументів». Він доречний, поки перевірок одна або дві; далі конструктор швидко перетворюється на купу if.
Варіант 2: об'єкт параметрів і значення за замовчуванням
Коли параметрів багато, зручніше приймати об'єкт:
class User {
constructor({ name, age = null, email = null } = {}) {
this.name = name;
this.age = age;
this.email = email;
}
}
const u1 = new User({ name: "Tim" });
const u2 = new User({ name: "Alex", email: "alex@example.com" });
console.log(u2);Виклик читається зрозуміліше, порядок аргументів перестає мати значення, а = {} дозволяє створити об'єкт узагалі без аргументів.
Для невеликої кількості полів вистачає звичайних значень за замовчуванням:
class Point {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
const a = new Point(); // (0, 0)
const b = new Point(10); // (10, 0)
const c = new Point(5, 7); // (5, 7)Один конструктор, але три легальні способи виклику.
Варіант 3: статичні фабричні методи
Це найпопулярніший підхід у реальних проєктах: замість кількох конструкторів ви робите кілька фабрик з іменами, які пояснюють намір.
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
static fromName(name) {
return new User(name, null);
}
static fromObject(obj) {
return new User(obj.name, obj.age);
}
}
const u1 = User.fromName("Tim");
const u2 = User.fromObject({ name: "Alex", age: 30 });
console.log(u1, u2);Формально це звичайні статичні методи, які повертають екземпляр, але для користувача класу вони виглядають саме як «різні конструктори» і мають промовисті назви на кшталт fromJson чи fromDatabaseRow.
Варіант 4: успадкування
Якщо різні способи створення означають різні набори полів і поведінки, краще виразити це ієрархією класів, де кожен нащадок має власний конструктор:
class User {
constructor(name) {
this.name = name;
}
}
class Admin extends User {
constructor(name, permissions) {
super(name);
this.permissions = permissions;
}
}
const u = new User("Tim");
const a = new Admin("Alex", ["edit", "delete"]);Зведення підходів:
| Підхід | Чи допустимо | Приклад |
|---|---|---|
Кілька constructor() | Ні, синтаксична помилка | немає |
| Один конструктор із перевіркою аргументів | Так | if (typeof age === "undefined") ... |
| Об'єкт параметрів | Так | constructor({ name, age }) {} |
| Фабричні методи | Так | User.fromName() / User.fromObject() |
| Успадкування | Так | class Admin extends User |
| Опціональні параметри | Так | constructor(x = 0, y = 0) |
Висновок: конструктор у класі один, але тієї самої гнучкості легко досягти перевірками, фабриками або успадкуванням.
Типові помилки
- Спроба перенести перевантаження конструкторів із Java чи C#. У JavaScript його немає взагалі, і помилка виникає ще на етапі парсингу.
- Конструктор, що розрісся до довгого ланцюга
if (arguments.length === ...). Це ознака того, що час перейти на фабричні методи. - Перевірка
if (!age)замістьif (typeof age === "undefined"): значення0і порожній рядок теж хибні, тож коректні дані випадково підмінюються значенням за замовчуванням. - Об'єкт параметрів без
= {}у кінці: тоді викликnew User()кинеTypeError, бо деструктуризуватиundefinedнеможливо. - Фабричний метод, який жорстко пише
new User(...)замістьnew this(...): у нащадку такий метод поверне батьківський тип.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.