2 березня 2024 р.

Методи об’єкта, "this"

Об’єкти зазвичай створюються для представлення сутностей реального світу, таких як користувачі, замовлення тощо:

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(); // Привіт!
Object-oriented programming

Коли ми пишемо наш код, використовуючи об’єкти для представлення сутностей, це називається об’єктно-орієнтоване програмування, скорочено: “ООП”.

ООП є великою предметною областю і цікавою наукою саме по собі. Як правильно обрати сутності? Як організувати взаємодію між ними? Це архітектура, і на цю тему є чудові книги, такі як “Шаблони проєктування: елементи багаторазового об’єктно-орієнтованого програмного забезпечення” Е. Гамми, Р. Хелма, Р. Джонсона, Дж. Віссідеса або “Об’єктно-орієнтований аналіз та дизайн з застосунками” Г. Буча та ін.

Скорочений запис методу

Існує коротший синтаксис для методів в літералі об’єкта:

// цей об’єкт робить те ж саме

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, що посилається на цей об’єкт.