Questo articolo tratta un argomento avanzato, utile per capire meglio alcuni casi limite.
Non di fondamentale importanza. Molti sviluppatori esperti vivono bene senza esserne a conoscenza. Continua la lettura se sei interessato a sapere come funzionano le cose internamente.
Uninvocazione di un metodo valutata dinamicamente pu perdere il this.
Ad esempio:
let user = {
name: "John",
hi() { alert(this.name); },
bye() { alert("Bye"); }
};
user.hi(); // funziona
// ora invochiamo user.hi o user.bye in base al nome
(user.name == "John" ? user.hi : user.bye)(); // Errore!
Nellultima riga abbiamo un operatore condizionale che sceglie tra user.hi o user.bye. In questo caso il risultato user.hi.
Successivamente il metodo viene invocato immediatamente con le parentesi (). Ma non funziona correttamente!
Come potete vedere, linvocazione genera un errore, perch il valore di "this" allinterno della chiamata diventa undefined.
Questo invece funziona (object punto metodo):
user.hi();
Questo no (valutazione del metodo):
(user.name == "John" ? user.hi : user.bye)(); // Errore!
Perch? Se vogliamo capire il motivo, dobbiamo addentrarci nei dettagli del funzionamento della chiamata obj.method().
Il tipo Reference spiegato
Guardando da pi vicino, possiamo notare due operazioni nellistruzione obj.method():
- Primo, il punto
'.'recupera la proprietobj.method. - Successivamente le parentesi
()la eseguono.
Quindi, come vengono passate le informazioni riguardo al this dalla prima alla seconda parte?
Se spostiamo queste istruzioni in righe separate, allora this verr sicuramente perso:
let user = {
name: "John",
hi() { alert(this.name); }
}
// dividiamo l'accesso e l'invocazione in due righe
let hi = user.hi;
hi(); // Errore, perch this undefined
Qui hi = user.hi assegna la funzione alla variabile, e nellultima riga completamente autonoma, quindi non si ha alcun this.
Per rendere linvocazione user.hi() funzionante, JavaScript applica un trucco il punto '.' non ritorna una funzione, ma piuttosto un valore del tipo speciale Reference.
Il tipo Reference un tipo descritto dalla specifica. Non possiamo utilizzarlo esplicitamente, ma viene utilizzato internamente dal linguaggio.
Il valore del tipo Reference una combinazione di tre valori (base, name, strict), dove:
baseloggetto.nameil nome della propriet.strictvale true seuse strictattivo.
Il risultato dellaccesso alla propriet user.hi non una funzione, ma un valore di tipo Reference. Per user.hi in strict mode vale:
// valore di tipo Reference
(user, "hi", true)
Quando le parentesi () vengono invocate in un tipo Reference, queste ricevono tutte le informazioni riguardo loggetto ed il metodo, e possono quindi impostare correttamente il valore di this (=user in questo caso).
Il tipo Reference uno speciale tipo intermedio utilizzato internamente, con lo scopo di passare le informazioni dal punto . allinvocazione con le parentesi ().
Qualsiasi altra operazione come un assegnazione hi = user.hi scarta completamente il tipo Reference, accede al valore user.hi (una funzione) e lo ritorna. Quindi qualsiasi ulteriore operazione perder this.
Quindi, come risultato, il valore di this viene passato correttamente solo se la funzione viene invocata direttamente utilizzando il punto obj.method() o la sintassi con le parentesi quadre obj['method']() (in questo caso si equivalgono). Esistono diversi modi per evitare questo problema, come func.bind().
Riepilogo
Il tipo Reference un tipo interno del linguaggio.
La lettura di una propriet, con il punto . in obj.method() non ritorna esattamente il valore della propriet, ma uno speciale tipo reference che memorizza sia il valore della propriet che loggetto a cui accedere.
Questo accade per consentire che la successiva invocazione con () imposti correttamente il this.
Per tutte le altre operazioni, il tipo reference diventa automaticamente il valore della propriet (una funzione nel nostro caso).
Il meccanismo descritto nascosto ai nostri occhi. Ha importanza solo in alcuni casi, ad esempio quando un metodo viene ottenuto dinamicamente dalloggetto, utilizzando unespressione.
Commenti
<code>, per molte righe includile nel tag<pre>, per pi di 10 righe utilizza una sandbox (plnkr, jsbin, codepen)