Skip to content

Latest commit

 

History

History
270 lines (187 loc) · 12.4 KB

File metadata and controls

270 lines (187 loc) · 12.4 KB

Методи об’єкта, "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(); // Привіт!
Коли ми пишемо наш код, використовуючи об’єкти для представлення сутностей, це називається [об’єктно-орієнтоване програмування](https://uk.wikipedia.org/wiki/Об'єктно-орієнтоване_програмування), скорочено: "ООП".

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

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

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

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

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.

````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`, то його значення береться ззовні.