Ця стаття охоплює складну тему, щоб краще зрозуміти певні крайні випадки.
Це не є важливим. Багато досвідчених розробників живуть добре, не знаючи цього. Прочитайте, якщо ви хочете знати, як певні речі працюють під капотом.
Динамічно оцінений виклик методу може втратити this
.
Наприклад:
let user = {
name: "Іван",
hi() { alert(this.name); },
bye() { alert("До побачення"); }
};
user.hi(); // працює
// тепер викличмо user.hi або user.bye залежно від назви
(user.name == "John" ? user.hi : user.bye)(); // Помилка!
На останньому рядку є умовний оператор, який вибирає або user.hi
або user.bye
. У цьому випадку результат – user.hi
.
Потім метод негайно викликається за допомогою дужок ()
. Але це працює неправильно!
Як ви бачите, виклик призводить до помилки, тому що значення "this"
всередині виклику стає undefined
.
Це працює (метод об’єкта через крапку):
user.hi();
Це ні (динамічно оцінений метод):
(user.name == "John" ? 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…)