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.
Unâinvocazione 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!
Nellâultima 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, lâinvocazione genera un errore, perché il valore di "this" allâinterno 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 nellâistruzione obj.method():
- Primo, il punto
'.'recupera la proprietÃobj.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 nellâultima riga è completamente autonoma, quindi non si ha alcun this.
Per rendere lâinvocazione 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:
baseè lâoggetto.nameè il nome della proprietà .strictvale true seuse strictè attivo.
Il risultato dellâaccesso 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 lâoggetto 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 . allâinvocazione 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 lâoggetto 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 dallâoggetto, utilizzando unâespressione.
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)