Ця стаття охоплює складну тему, щоб краще зрозуміти певні крайні випадки.
Це не є важливим. Багато досвідчених розробників живуть добре, не знаючи цього. Прочитайте, якщо ви хочете знати, як певні речі працюють під капотом.
Динамічно обчислений виклик методу може втратити this.
Наприклад:
let user = {
name: "Іван",
hi() { alert(this.name); },
bye() { alert("До побачення"); }
};
user.hi(); // працює
// тепер викличмо user.hi або user.bye залежно від назви
(user.name == "Іван" ? user.hi : user.bye)(); // Помилка!
На останньому рядку є умовний оператор, який вибирає або user.hi або user.bye. У цьому випадку результат – user.hi.
Потім метод негайно викликається за допомогою дужок (). Але це працює неправильно!
Як ви бачите, виклик призводить до помилки, тому що значення "this" всередині виклику стає undefined.
Це працює (метод об’єкта через крапку):
user.hi();
Це ні (динамічно обчислений метод):
(user.name == "Іван" ? user.hi : user.bye)(); // Помилка!
Чому? Якщо ми хочемо зрозуміти, чому це трапляється, загляньмо під капот, як працює виклик obj.method().
Пояснення Посилального типу
Дивлячись уважно, ми можемо помітити дві операції в інструкції obj.method():
- По-перше, крапка
'.'витягує властивістьobj.method. - Потім дужки
()виконують її.
Але як інформація про this передається з першої частини до другої?
Якщо ми поставимо ці операції на окремі рядки, то this буде втрачено:
let user = {
name: "John",
hi() { alert(this.name); }
};
// розділимо отримання та виклик методу на два рядки
let hi = user.hi;
hi(); // Помилка, тому що this - це undefined
Тут hi = user.hi поміщає функцію в змінну, а на наступному рядку, ця змінна повністю автономна, і тому не має this.
Щоб зробити виклик user.hi() робочим, JavaScript використовує трюк – крапка '.' повертає не функцію, а значення спеціального посилального типу.
Посилальний тип – це “тип специфікації”. Ми не можемо явно використовувати його, але він використовується всередині мови.
Значення посилального типу – це комбінація трьох значень (base, name, strict), де:
base– це об’єкт.name– це назва властивості.strict– це true якщо дієuse strict.
Результат доступу до властивості user.hi є не функцією, а значенням посилального типу. Для user.hi у суворому режимі це:
// Значення посилального типу
(user, "hi", true)
Коли дужки () викликаються з посилальним типом, вони отримують повну інформацію про об’єкт та його метод, і можуть встановити правильний this (user у нашому випадку).
Посилальний тип – це особливий “посередницький” внутрішній тип, який використовується з метою передачі інформації від крапки . до дужок виклику ().
Будь-яка інша операція, наприклад присвоєння hi = user.hi в цілому відкидає посилальний тип та приймає значення user.hi (функції) і передає його. Отже, будь-яка подальша операція “втрачає” this.
Отже, як результат, значення this передається правильно тільки тоді, коли функція викликається безпосередньо за допомогою крапки obj.method() або синтаксису квадратних дужок obj['method']() (вони роблять одне й те ж саме). Існують різні способи розв’язання цієї проблеми, наприклад func.bind().
Підсумки
Посилальний тип – це внутрішній тип мови.
Читання властивості, наприклад, крапкою . в obj.method() повертає не саме значення властивості, але спеціальне значення “посилального типу”, яке зберігає як значення властивості, так і об’єкт, з якою він був взятий.
Це використовується для подальшого виклику методу за допомогою (), щоб отримати об’єкт і встановити this перед цим.
Для всіх інших операцій, посилальний тип автоматично стає значенням властивості (функцією у нашому випадку).
Уся ця механіка прихована від наших очей. Це важливо лише в тонких випадках, наприклад, коли метод отримується динамічно з об’єкта, використовуючи вираз.
Коментарі
<code>, для кількох рядків – обгорніть їх тегом<pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)