Об’єкти зазвичай створюються для представлення сутностей реального світу, таких як користувачі, замовлення тощо:
let user = {
name: "Іван",
age: 30
};
І в реальному світі користувач може діяти: вибрати щось із кошика для покупок, авторизуватися, виходити із системи тощо.
Дії представлені в JavaScript функціями у властивостях об’єкта.
Для початку навчімо user
вітатися:
let user = {
name: "Іван",
age: 30
};
*!*
user.sayHi = function() {
alert("Привіт!");
};
*/!*
user.sayHi(); // Привіт!
Тут ми щойно створили функцію з допомогою Function Expression (функціональний вираз) та присвоїли її у властивість об’єкта user.sayHi
.
Потім ми викликали її завдяки user.sayHi()
. Користувач тепер може говорити!
Функція, яка є властивістю об’єкта, називається його методом.
Отже, ми отримали метод sayHi
об’єкта user
.
Звісно ж, ми могли б використовувати і попередньо оголошену функцію як метод, наприклад:
let user = {
// ...
};
*!*
// спочатку створимо функцію
function sayHi() {
alert("Привіт!");
}
// потім додамо її як метод
user.sayHi = sayHi;
*/!*
user.sayHi(); // Привіт!
Коли ми пишемо наш код, використовуючи об’єкти для представлення сутностей, це називається [об’єктно-орієнтоване програмування](https://uk.wikipedia.org/wiki/Об'єктно-орієнтоване_програмування), скорочено: "ООП".
ООП є великою предметною областю і цікавою наукою саме по собі. Як правильно обрати сутності? Як організувати взаємодію між ними? Це архітектура, і на цю тему є чудові книги, такі як "Шаблони проєктування: елементи багаторазового об’єктно-орієнтованого програмного забезпечення" Е. Гамми, Р. Хелма, Р. Джонсона, Дж. Віссідеса або "Об’єктно-орієнтований аналіз та дизайн з застосунками" Г. Буча та ін.
Існує коротший синтаксис для методів в літералі об’єкта:
// цей об’єкт робить те ж саме
user = {
sayHi: function() {
alert("Привіт!");
}
};
// скорочений метод виглядає краще, чи не так?
user = {
*!*
sayHi() { // те ж саме що й "sayHi: function(){...}"
*/!*
alert("Привіт!");
}
};
Як було показано, ми можемо опустити "function"
і написати просто sayHi()
.
Слід відзначити, що ці позначення не є повністю ідентичними. Існують тонкі відмінності, пов’язані з наслідуванням об’єктів (про які піде мова пізніше), але наразі вони не мають значення. Майже у всіх випадках скорочений синтаксис краще.
Як правило, метод об’єкта повинен отримувати доступ до інформації, що зберігається в об’єкті, для виконання своєї роботи.
Наприклад, коду всередині user.sayHi()
може знадобитися ім’я, що зберігається в об’єкті user
.
Для доступу до інформації всередині об’єкта метод може використовувати ключове слово this
.
Значенням this
є об’єкт "перед крапкою", який використовується для виклику методу.
Наприклад:
let user = {
name: "Іван",
age: 30,
sayHi() {
*!*
// "this" - це "поточний об’єкт"
alert(this.name);
*/!*
}
};
user.sayHi(); // Іван
Тут під час виконання коду user.sayHi()
, значенням this
буде user
.
Також можна отримати доступ до об’єкта без цього, посилаючись на нього через зовнішню змінну:
let user = {
name: "Іван",
age: 30,
sayHi() {
*!*
alert(user.name); // використовуємо змінну "user" замість "this"
*/!*
}
};
...Але такий код ненадійний. Якщо ми вирішимо скопіювати user
в іншу змінну, напр. admin = user
перезаписати user
чимось іншим, тоді цей код отримає доступ до неправильного об’єкта.
Це продемонстровано нижче:
let user = {
name: "Іван",
age: 30,
sayHi() {
*!*
alert( user.name ); // призводить до помилки
*/!*
}
};
let admin = user;
user = null; // перезапишемо значення змінної для наочності
*!*
admin.sayHi(); // TypeError: Cannot read property 'name' of null
*/!*
Якщо ми використовуємо this.name
замість user.name
всередині alert
, тоді цей код буде працювати.
В JavaScript, ключове слово this
поводить себе не так, як в більшості мов програмування.
В цьому коді немає синтаксичної помилки:
function sayHi() {
alert( *!*this*/!*.name );
}
Значення this
обчислюється під час виконання і залежить від контексту.
Наприклад, тут одна й та ж функція присвоєна двом різним об’єктам і має різний "this" при викликах:
let user = { name: "Іван" };
let admin = { name: "Адмін" };
function sayHi() {
alert( this.name );
}
*!*
// використовуємо одну і ту ж функцію у двох об’єктах
user.f = sayHi;
admin.f = sayHi;
*/!*
// виклики функцій, приведені нижче, мають різні this
// "this" всередині функції є посиланням на об’єкт "перед крапкою"
user.f(); // Іван (this == user)
admin.f(); // Адмін (this == admin)
admin['f'](); // Адмін (неважливо те, як звертатися до методу об’єкта - через крапку чи квадратні дужки)
Правило просте: якщо obj.f()
викликано, то this
це obj
під час виконання f
. Так що в даному прикладі це user
або admin
.
````smart header="Виклик без об’єкта: this == undefined
"
Ми можемо навіть викликати функцію взагалі без об’єкта:
function sayHi() {
alert(this);
}
sayHi(); // undefined
В такому випадку this
є undefined
в суворому режимі ("use strict"
). Якщо ми спробуємо звернутися до this.name
трапиться помилка.
У несуворому режимі значенням this
в такому випадку буде глобальний об’єкт (window
у браузері, ми дійдемо до нього пізніше в главі ). Це поведінка, яка склалася історично та виправляється завдяки використанню суворого режиму ("use strict"
).
Зазвичай такий виклик є помилкою програмування. Якщо всередині функції є this
, вона очікує виклику в контексті об’єкта.
```smart header="Наслідки неприв'язаного (англ. unbound) `this`"
Якщо ви прийшли з іншої мови програмування, то ви, мабуть, звикли до ідеї "зв’язаного `this`", де методи, визначені в об’єкті, завжди мають `this`, що посилається на цей об’єкт.
В JavaScript `this` є "вільним", його значення обчислюється під час виклику і залежить не від того, де метод був оголошений, а від того, який об’єкт "перед крапкою".
Поняття `this`, що визначається в процесі роботи має як плюси, так і мінуси. З одного боку, функцію можна використовувати повторно для різних об’єктів. З іншого боку, більша гнучкість створює більше можливостей для помилок.
Тут наша позиція полягає не в тому, щоб судити, добре чи погане таке рішення щодо дизайну мови. Ми зрозуміємо, як з цим працювати, як отримати переваги та уникнути проблем.
```
## Стрілкові функції не мають "this"
Стрілкові функції особливі: у них немає "свого" `this`. Якщо ми посилаємось на `this` з такої функції, його значення береться із зовнішньої "нормальної" функції.
Наприклад, тут `arrow()` використовує `this` із зовнішнього `user.sayHi()` методу:
```js run
let user = {
firstName: "Ілля",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ілля
```
Це особливість стрілкових функцій є корисною коли ми не хочемо мати окреме `this`, а лише взяти його із зовнішнього контексту. Далі в главі <info:arrow-functions> ми детальніше розглянемо стрілкові функції.
## Підсумки
- Функції, які зберігаються у властивостях об’єкта, називаються "методами".
- Методи дозволяють об’єктам "діяти" як от `object.doSomething()`.
- Методи можуть посилатися на об’єкт завдяки `this`.
- Значення `this` визначається під час виконання.
- Коли функція оголошена, вона може використовувати `this`, але саме `this` не має значення, доки функція не буде викликана.
- Функцію можна копіювати між об’єктами.
- Коли функція викликається в синтаксисі "методу": `object.method()`, значення `this` під час виклику є `object` – об’єкт перед крапкою.
Зверніть увагу, що стрілкові функції є особливими: у них немає `this`. Коли всередині стрілкової функції звертаються до `this`, то його значення береться ззовні.