La confusione su this nasce da un equivoco: il suo valore dipende da come chiami la funzione, non da dove è scritta.
const user = {
name: "Ale",
saluta() { return "Ciao " + this.name; },
};
user.saluta(); // "Ciao Ale" -> this = user
const f = user.saluta;
f(); // "Ciao undefined" -> this perso
f.call(user); // "Ciao Ale" -> this forzato
const legato = user.saluta.bind(user);
legato(); // "Ciao Ale" -> this legato per sempre
Chiamata come metodo (user.saluta()): this è l'oggetto. Chiamata "sciolta" (f()): this si perde. Con call/apply/bind lo imposti tu.
Le arrow function sono l'eccezione salvavita: non hanno un proprio this, ereditano quello del contesto in cui sono definite. Per questo nei callback (setTimeout(() => this.x)) risolvono metà dei problemi.