Звичайний синтаксис {...}
дозволяє створити тільки один об’єкт. Проте часто нам потрібно створити багато однотипних об’єктів, таких як, наприклад, користувачі чи елементи меню тощо.
Це можна зробити за допомогою функції-конструктора та оператора "new"
.
Функція-конструктор
Технічно, функції-конструктори – це звичайні функції. Однак є дві загальні домовленості:
- Ім’я функції-конструктора повинно починатися з великої літери.
- Функції-конструктори повинні виконуватися лише з оператором
"new"
.
Наприклад:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Джек");
alert(user.name); // Джек
alert(user.isAdmin); // false
Коли функція виконується з new
, відбуваються наступні кроки:
- Створюється новий порожній об’єкт, якому присвоюється
this
. - Виконується тіло функції. Зазвичай воно модифікує
this
, додає до нього нові властивості. - Повертається значення
this
.
Інакше кажучи, виклик new User(...)
робить щось на зразок:
function User(name) {
// this = {}; (неявно)
// додає властивості до this
this.name = name;
this.isAdmin = false;
// return this; (неявно)
}
Отже, let user = new User("Джек")
дає той самий результат, що:
let user = {
name: "Джек",
isAdmin: false
};
Тепер, якщо ми хочемо створити інших користувачів, ми можемо викликати new User("Ганна")
, new User("Аліса")
тощо. Така конструкція значно коротша, ніж використання літералів кожного разу, а також легша для читання.
Це і є основною метою конструкторів – зручне перевикористання коду зі створення об’єктів.
Ще раз зауважимо – технічно будь-яка функція (окрім стрілкових функцій, оскільки вони не мають власного this
) може бути використана як конструктор. Вона може бути запущена через new
, і буде виконано наведений вище алгоритм. “Ім’я з великої літери” це загальна домовленість, яка допомагає чітко зрозуміти, що функцію слід запускати з new
.
Якщо у нас є багато рядків коду, які створюють єдиний складний об’єкт, ми можемо обернути їх у функцію-конструктор, яка одразу буде викликана, таким чином:
// створити функцію і негайно викликати її за допомогою new
let user = new function() {
this.name = "Джон";
this.isAdmin = false;
// ...інший код для створення користувача
// можливо складна логіка та інструкції
// локальні змінні тощо
};
Цей конструктор не можна викликати знову, оскільки він ніде не зберігається, а лише створюється і викликається. Отже, такий прийом спрямований на інкапсуляцію коду, який створює єдиний об’єкт, без подальшого повторного використання.
Перевірка виклику у режимі конструктора: new.target
Синтаксис з цього розділу використовується рідко, пропустіть його, якщо наразі ви не хочете занурюватись більш детально у мову.
Використовуючи спеціальну властивість new.target
всередині функції, ми можемо перевірити чи була ця функція викликана за допомогою оператора new
чи без нього.
Якщо функція була викликана за допомогою new
, то в new.target
буде сама функція, в іншому разі отримаємо undefined
:
function User() {
alert(new.target);
}
// виклик без "new":
User(); // undefined
// виклик з "new":
new User(); // function User { ... }
Така можливість може бути використана всередині функції для того, щоб дізнатися чи функція була викликана за допомогою оператора new
, “у режимі конструктора”, чи без нього, “у звичайному режимі”.
Ми також можемо зробити, щоб обидва виклики, з new
та звичайний, робили одне й те саме, таким чином:
function User(name) {
if (!new.target) { // якщо ви викликали без оператора new
return new User(name); // ...додамо оператор new за вас
}
this.name = name;
}
let john = User("Джон"); // перенаправляє виклик до new User
alert(john.name); // Джон
Цей підхід іноді використовується в бібліотеках для створення більш гнучкого синтаксису, який дозволив би розробникам викликати функцію як з оператором new
, так і без нього.
Проте використання такого підходу скрізь не є хорошою практикою, тому що відсутність new
робить код менш очевидним. З оператором new
ми усі знаємо, що буде створено новий об’єкт.
Повернення значення з конструктора return
Зазвичай конструктори не мають інструкції return
. Їх завдання – записати усе необхідне у this
, яке автоматично стане результатом.
Але якщо є інструкція return
, то застосовується просте правило:
- Якщо
return
викликається з об’єктом, тоді замістьthis
буде повернено цей об’єкт. - Якщо
return
викликається з примітивом, примітив ігнорується.
Інакше кажучи, return
з об’єктом повертає цей об’єкт, у всіх інших випадках повертається this
.
У наступному прикладі return
перезаписує this
, повертаючи об’єкт:
function BigUser() {
this.name = "Джон";
return { name: "Ґодзілла" }; // <-- повертає цей об’єкт
}
alert( new BigUser().name ); // Ґодзілла, отримали цей об’єкт
А ось приклад з порожнім return
(або ми можемо розмістити примітив після нього, не має значення):
function SmallUser() {
this.name = "Джон";
return; // <-- повертає this
}
alert( new SmallUser().name ); // Джон
Зазвичай конструктори не мають інструкції return
. Тут ми згадуємо особливу поведінку з поверненнями об’єктів, головним чином, для повноти вивчення мови.
До речі, ми можемо опустити дужки після new
, якщо виклик конструктора відбувається без аргументів:
let user = new User; // <-- немає дужок
// те саме, що
let user = new User();
Пропуск дужок не є гарною практикою, та специфікація дозволяє такий синтаксис.
Створення методів у конструкторі
Використання конструкторів для створення об’єктів дає велику гнучкість. Конструктор може мати параметри, які визначають, як побудувати об’єкт і що в нього помістити.
Звичайно, ми можемо додати до this
не лише властивості, але й методи.
У наведеному нижче прикладі, new User(name)
створює об’єкт із заданим name
та методом sayHi
:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "Моє ім’я: " + this.name );
};
}
let john = new User("Джон");
john.sayHi(); // Моє ім’я: Джон
/*
john = {
name: "Джон",
sayHi: function() { ... }
}
*/
Для створення складних об’єктів існує більш просунутий синтаксис, класи, про який ми поговоримо пізніше.
Підсумки
- Функції-конструктори або, коротко, конструктори, є звичайними функціями, але існує загальна домовленість ім’я такої функції починати з великої літери.
- Конструктори повинні викликатися тільки з використанням оператора
new
. Такий виклик передбачає створення порожньогоthis
на початку та повернення заповненого у кінці.
Ми можемо використовувати конструктори для створення численних подібних об’єктів.
JavaScript надає функції-конструктори для багатьох вбудованих об’єктів мови: наприклад, Date
, Set
та інших, які ми плануємо вивчати далі.
У цьому розділі ми розглядаємо лише основи об’єктів та конструкторів. Вони є важливими для подальшого вивчення типів даних та функцій у наступних розділах.
Після того, як ми це вивчемо, ми повернемося до об’єктів для більш детального їх вивчення у розділах Прототипи, наслідування та Класи.