Об’єкти зазвичай створюються для представлення сутностей реального світу, таких як користувачі, замовлення тощо:
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(); // Привіт!
Коли ми пишемо наш код, використовуючи об’єкти для представлення сутностей, це називається об’єктно-орієнтоване програмування, скорочено: “ООП”.
ООП є великою предметною областю і цікавою наукою саме по собі. Як правильно обрати сутності? Як організувати взаємодію між ними? Це архітектура, і на цю тему є чудові книги, такі як “Шаблони проєктування: елементи багаторазового об’єктно-орієнтованого програмного забезпечення” Е. Гамми, Р. Хелма, Р. Джонсона, Дж. Віссідеса або “Об’єктно-орієнтований аналіз та дизайн з застосунками” Г. Буча та ін.
Скорочений запис методу
Існує коротший синтаксис для методів в літералі об’єкта:
// цей об’єкт робить те ж саме
user = {
sayHi: function() {
alert("Привіт!");
}
};
// скорочений метод виглядає краще, чи не так?
user = {
sayHi() { // те ж саме що й "sayHi: function(){...}"
alert("Привіт!");
}
};
Як було показано, ми можемо опустити "function"
і написати просто sayHi()
.
Слід відзначити, що ці позначення не є повністю ідентичними. Існують тонкі відмінності, пов’язані з наслідуванням об’єктів (про які піде мова пізніше), але наразі вони не мають значення. Майже у всіх випадках скорочений синтаксис краще.
“this” в методах
Як правило, метод об’єкта повинен отримувати доступ до інформації, що зберігається в об’єкті, для виконання своєї роботи.
Наприклад, коду всередині 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
, тоді цей код буде працювати.
“this” не є фіксованим
В 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
.
this == undefined
Ми можемо навіть викликати функцію взагалі без об’єкта:
function sayHi() {
alert(this);
}
sayHi(); // undefined
В такому випадку this
є undefined
в суворому режимі ("use strict"
). Якщо ми спробуємо звернутися до this.name
трапиться помилка.
У несуворому режимі значенням this
в такому випадку буде глобальний об’єкт (window
у браузері, ми дійдемо до нього пізніше в главі Глобальний об’єкт). Це – поведінка, яка склалася історично та виправляється завдяки використанню суворого режиму ("use strict"
).
Зазвичай такий виклик є помилкою програмування. Якщо всередині функції є this
, вона очікує виклику в контексті об’єкта.
this
Якщо ви прийшли з іншої мови програмування, то ви, мабуть, звикли до ідеї “зв’язаного this
”, де методи, визначені в об’єкті, завжди мають this
, що посилається на цей об’єкт.
В JavaScript this
є “вільним”, його значення обчислюється під час виклику і не залежить від того, де метод був оголошений, а від того, який об’єкт “перед крапкою”.
Поняття this
, що визначається в процесі роботи має як плюси, так і мінуси. З одного боку, функцію можна використовувати повторно для різних об’єктів. З іншого боку, більша гнучкість створює більше можливостей для помилок.
Тут наша позиція полягає не в тому, щоб судити, добре чи погане таке рішення щодо дизайну мови. Ми зрозуміємо, як з цим працювати, як отримати переваги та уникнути проблем.
Стрілкові функції не мають “this”
Стрілкові функції особливі: у них немає “свого” this
. Якщо ми посилаємось на this
з такої функції, його значення береться із зовнішньої “нормальної” функції.
Наприклад, тут arrow()
використовує this
із зовнішнього user.sayHi()
методу:
let user = {
firstName: "Ілля",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ілля
Це особливість стрілкових функцій є корисною коли ми не хочемо мати окреме this
, а лише взяти його із зовнішнього контексту. Далі в главі Повторення стрілкових функцій ми детальніше розглянемо стрілкові функції.
Підсумки
-
Функції, які зберігаються у властивостях об’єкта, називаються “методами”.
-
Методи дозволяють об’єктам “діяти” подібно до
object.doSomething()
. -
Методи можуть посилатися на об’єкт завдяки
this
. -
Значення
this
визначається під час виконання. -
Коли функція оголошена, вона може використовувати
this
, але самеthis
не має значення, доки функція не буде викликана. -
Функцію можна копіювати між об’єктами.
-
Коли функція викликається в синтаксисі “методу”:
object.method()
, значенняthis
під час виклику єobject
– об’єкт перед крапкою.
Зверніть увагу, що стрілкові функції є особливими: у них немає this
. Коли всередині стрілкової функції звертаються до this
, то його значення береться ззовні.