Ця стаття охоплює складну тему, щоб краще зрозуміти певні крайні випадки.
Це не є важливим. Багато досвідчених розробників живуть добре, не знаючи цього. Прочитайте, якщо ви хочете знати, як певні речі працюють під капотом.
Динамічно обчислений виклик методу може втратити 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…)