Почему в этом случае происходит потеря значения this?

Читаю об этом на learn.js и ничего не понимаю.

Можно ли как-то простыми словами объяснить почему во втором случае происходит потеря значения this?

let user = {
    name: "Джон",
    hi() { console.log(this.name); },
    bye() { console.log("Пока"); }
};

user.hi(); // вызов метода работает хорошо
(user.name == "Джон" ? user.hi : user.bye)(); // Ошибка!

Тут, насколько я понимаю, если значение свойства name Джон, то происходит выбор первого значения - user.hi. Дальше открываются скобки и происходит запуск метода объекта.

Чем глобально первый запуск отличается от второго? Первый выглядит так: user.hi(). Второй в итоге будет выглядеть так: (user.hi()).


Ответы (2 шт):

Автор решения: vsemozhebuty
(user.name == "Джон" ? user.hi : user.bye)();

Можно считать сокращённой записью для:

let func;
if(user.name == "Джон") func = user.hi;
else func = user.bye;

func(); // контекст вызова потерян

Просто в певом случае ссылка на метод сохраняется не в переменную, в как неименованное промежуточное значение, полученное из выражения в скобках.

→ Ссылка
Автор решения: OPTIMUS PRIME

user.hi() — В первом случае, когда происходит обычный вызов метода, this ссылается на «объект перед точкой»user;

Этот механизм работает во всех случаях (почти *), где используется вызов объект.метод();


(user.name == "Джон" ? user.hi : user.bye)(); — Не одно и то же, что и (user.hi()) или (user.hi)().

Здесь сначала выполняется выражение внутри скобок, возвращается свойство hi из объекта user (ссылка на функцию), к которому затем применяется оператор вызова функции ()

А у ссылки на функцию нет никакого контекста (нет объекта перед точкой, и не важно, откуда была получена ссылка). Т.е. ('x' ? user.hi : user.bye) всего лишь один из способов получения ссылки. Ошибка будет возникать и в случае обычного копирования в переменную:

"use strict";

let user = {
    hi() { console.log(this.name); },
};

let func = user.hi; // Ссылка на функцию скопирована в переменную func

func(); // У ссылки уже нет никакого контекста (ни точки, ни объекта перед ней)


setTimeout(user.hi, 1000) — тоже частая вариация такой ошибки. Вытащили функцию hi из объекта, передали в setTimeout - контекст потерялся.
Правильно: setTimeout(() => user.hi(), 1000) — передается функция () => user.hi() которая будет вызвана через секунду и сама вызовет user.hi(), контекст никуда не денется.


* Раз в никогда может возникнуть ситуация, когда в объект попала функция, у которой есть свой жестко привязанный контекст:

function fn() {
  console.log(this.a);
}

/***/
let obj1 = {
  a: 100,
};

let obj2 = {
  a: 999,
  func: fn.bind(obj1),
}

// Function.bind(obj) создает и возвращает новую функцию, внутри которой
// this всегда будет равен obj, независимо от способа вызова;

obj2.func();
obj2.func.call(obj2);

→ Ссылка