this si decide al momento della chiamata
Quasi tutta la confusione su this nasce da un presupposto sbagliato: che this dipenda da dove una funzione è definita. Non è così. this dipende da come la funzione viene chiamata.
Guarda la stessa funzione chiamata in due modi:
Stessa funzione, this diverso. La prima chiamata ha user a sinistra del punto, quindi this è user. La seconda non ha niente a sinistra, quindi this è undefined. La funzione non è cambiata: è cambiato il punto di chiamata.
Tieni a mente questa idea: per capire cosa vale this, guarda la chiamata, non la definizione.
Le quattro regole di binding
Ci sono quattro modi in cui this viene impostato. Quasi ogni domanda su this che ti capiterà trova risposta capendo quale dei quattro si applica.
1. Chiamata di metodo: obj.fn()
Quando una funzione viene chiamata come proprietà di un oggetto, this è quell'oggetto:
Quello che sta a sinistra del punto diventa this. Tutto qui.
2. Chiamata semplice: fn()
Se la chiami senza nessun oggetto davanti, this è undefined in strict mode (che moduli e classi attivano automaticamente) oppure l'oggetto globale in sloppy mode:
È da qui che vengono gli errori "this is undefined". Staccare un metodo dal suo oggetto trasforma una chiamata di metodo in una chiamata semplice:
Nessun counter. prima della chiamata, nessun binding. La funzione non ricorda l'oggetto da cui proviene.
3. Binding esplicito: .call(), .apply(), .bind()
Puoi forzare this al valore che vuoi:
.call e .apply chiamano subito la funzione; differiscono solo nel modo in cui passi gli argomenti. .bind restituisce una nuova funzione con this bloccato in modo permanente: utile per le callback.
4. Chiamata con new: new Fn()
Chiamare una funzione con new crea un oggetto nuovo e lo lega a this:
Le classi usano questo meccanismo dietro le quinte. Le incontreremo in un capitolo successivo.
Le arrow function non hanno un proprio this
Le arrow function infrangono di proposito le regole viste sopra. Non fanno alcun binding di this: lo ereditano dallo scope circostante, fissato nel momento in cui la arrow viene definita:
La arrow al livello più alto di un modulo cattura il this del modulo, che è undefined. Anche se viene chiamata come user.arrow(), la arrow si rifiuta di cambiare binding.
Sembra un bug, ma è proprio lo scopo. Le arrow function danno il meglio dentro i metodi, quando vuoi mantenere il this esterno:
La arrow dentro setInterval eredita this da start, che è stato chiamato come timer.start(). Quindi this.seconds funziona. Una normale function lì avrebbe avuto un proprio this (quello che passa setInterval) e si sarebbe rotta.
Regola pratica: usa le arrow function per le callback dentro i metodi. Usa le funzioni normali per i metodi stessi.
La classica trappola delle callback
È il modo più comune in cui this va storto. Passi un metodo come callback e perde il suo binding:
setTimeout chiama la funzione come chiamata semplice, non come c.increment(). Ci sono tre modi per risolvere:
Funzionano tutti e tre. Avvolgerlo in una arrow di solito è la soluzione più chiara.
this al livello più alto
Il this di livello superiore dipende da dove gira il tuo codice:
- Script nel browser (non modulo):
thisèwindow. - Modulo ES (compresa la maggior parte del codice moderno passato da un bundler):
thisèundefined. - Modulo CommonJS di Node.js:
thisèmodule.exports.
Per un riferimento affidabile all'oggetto globale in qualsiasi ambiente, usa globalThis:
In pratica non dovresti fare affidamento sul this di livello superiore. Usa globalThis quando ti serve davvero l'oggetto globale, e negli altri casi passa i valori in modo esplicito.
Un rapido albero decisionale
Quando vedi this e non sei sicuro di cosa varrà, segui questo elenco:
- La funzione è una arrow function? Allora
thisè quello dello scope che la contiene. Ignora del tutto il punto di chiamata. - È stata chiamata con
new? Allorathisè il nuovo oggetto. - È stata chiamata con
.call,.applyo come funzione legata con bind? Allorathisè il valore passato. - È stata chiamata come
obj.method()? Allorathisèobj. - È stata chiamata come semplice
fn()? Allorathisèundefinedin strict mode.
Questa scala, in quest'ordine, risolve ogni caso.
Prossimo passo: le funzioni di ordine superiore
Ora che this non è più un mistero, sei pronto per lo schema che rende JavaScript così espressivo: passare le funzioni come valori. Nella prossima pagina vedremo le funzioni di ordine superiore, cioè funzioni che ricevono o restituiscono altre funzioni, e come stanno alla base dei metodi degli array, dei gestori di eventi e della maggior parte del codice JavaScript reale.
Domande frequenti
A cosa si riferisce this in JavaScript?
this in JavaScript?this si riferisce all'oggetto su cui viene chiamata una funzione, non al punto in cui la funzione è stata definita. In user.greet(), this è user. In una semplice chiamata greet(), this è undefined in strict mode (oppure l'oggetto globale in sloppy mode). La regola riguarda il punto di chiamata, non quello di definizione.
Perché this è undefined dentro la mia funzione?
this è undefined dentro la mia funzione?Probabilmente hai staccato un metodo dal suo oggetto e l'hai chiamato da solo, oppure l'hai passato come callback. const fn = user.greet; fn(); perde il binding: al momento della chiamata non c'è nessun oggetto a sinistra del punto. Risolvi con .bind(user), avvolgendolo in una arrow function, oppure chiamandolo come user.greet().
In cosa è diverso this nelle arrow function?
this nelle arrow function?Le arrow function non hanno un proprio this. Lo ereditano dallo scope circostante nel momento in cui vengono definite. Questo le rende ideali come callback dentro i metodi, quando vuoi mantenere il this esterno. Significa anche che .call(), .apply() e .bind() non possono cambiare il this di una arrow function.
Cos'è this al livello più alto di uno script?
this al livello più alto di uno script?In un normale script nel browser, il this di livello superiore è l'oggetto window. In un modulo ES è undefined. Nei moduli CommonJS di Node.js è module.exports. Il riferimento valido in ogni ambiente è globalThis, che punta sempre all'oggetto globale.