Tre parole chiave, un solo compito
JavaScript ha tre modi di dichiarare una variabile: var, let e const. Tutti e tre legano un nome a un valore, ma differiscono per scope, regole di riassegnazione e comportamento prima che la dichiarazione venga eseguita. Il codice moderno usa const quasi sempre, let quando un valore deve cambiare e var raramente, se non mai.
La versione breve:
Il resto di questa pagina spiega perché quelle tre righe si comportano così.
const: la scelta di default
const crea un binding che non si può riassegnare. Una volta scritto const x = 5, non puoi più scrivere x = 6: il motore lancerà un TypeError.
Questa è tutta la regola. Siccome la maggior parte delle variabili in un programma ben scritto non ha bisogno di essere riassegnata, const va bene nella stragrande maggioranza dei casi. Scegliere const per primo fa risaltare i punti in cui un valore cambia davvero.
Una fonte comune di confusione: const protegge il binding, non il valore. Se il valore è un oggetto o un array, il suo contenuto resta modificabile:
const significa "questo nome punta sempre a questo oggetto". Non significa "questo oggetto non cambia mai". Se ti serve congelare l'oggetto stesso, lo strumento è Object.freeze(user), ma in pratica la maggior parte del codice si affida semplicemente alla convenzione che gli oggetti const non vadano modificati.
let: quando devi davvero riassegnare
let è identico a const in tutto, tranne che puoi riassegnarlo. Usalo per contatori, accumulatori, variabili di ciclo e ovunque un valore cambi davvero nel tempo.
Se ti accorgi di aver scritto let senza mai riassegnare la variabile, cambiala in const. Nella maggior parte dei progetti te lo farà notare il linter.
Scope di blocco
Sia let sia const hanno scope di blocco. Un blocco è tutto ciò che sta tra { e }: il corpo di un if, di un for, di una funzione, o anche un blocco { ... } isolato. Una variabile dichiarata dentro un blocco non esiste fuori da esso.
È proprio ciò che vuoi. Mantiene le variabili confinate al lavoro a cui appartengono e rende impossibili le collisioni accidentali tra nomi.
var non fa questo, ed è il motivo principale per evitarlo.
var: scope di funzione e sorprese
var ha come scope la funzione più vicina, non il blocco più vicino. Questo significa che un var dichiarato dentro un if o un for esce fuori, nella funzione che lo contiene:
Questo scope poco rigido è l'origine di una lunga lista di bug classici di JavaScript. Il più famoso riguarda i cicli in cui ogni iterazione condivide lo stesso var i e tutte le callback finiscono per vedere il valore finale.
var ti permette anche di ridichiarare lo stesso nome nello stesso scope senza lamentarsi, il che nasconde gli errori di battitura:
Il codice moderno usa quasi esclusivamente let e const. var compare nei progetti legacy, nelle vecchie risposte di Stack Overflow e negli script che devono girare in ambienti molto datati.
Hoisting e temporal dead zone
Tutte e tre le dichiarazioni subiscono l'hoisting: il motore le conosce prima di eseguire il codice del blocco. Però si comportano diversamente prima che la riga della dichiarazione venga eseguita.
var subisce l'hoisting e viene inizializzato a undefined. Puoi farvi riferimento prima della riga con var senza errori:
let e const subiscono l'hoisting ma non vengono inizializzati. Toccarli prima della dichiarazione lancia un errore. Questa finestra tra l'ingresso nello scope e l'esecuzione della dichiarazione si chiama temporal dead zone (TDZ):
La TDZ è una funzionalità, non un bug. Trasforma "usato prima di essere dichiarato" da un undefined silenzioso in un errore ben visibile, e così intercetta molti errori di battitura e di ordine.
La variabile const nel ciclo for...of
Un caso piccolo ma comune: i cicli for...of creano un nuovo binding a ogni iterazione, quindi puoi usare const per la variabile del ciclo anche se "cambia" da un'iterazione all'altra.
Ogni iterazione ha il proprio binding name: non c'è un'unica variabile che viene riassegnata. Un classico for (let i = 0; i < n; i++) richiede invece let, perché i è un solo binding che viene incrementato.
Una regola pratica
Scegli le dichiarazioni in questo ordine di preferenza:
constdi default. Se il valore non verrà riassegnato, dichiaralo.letquando il binding deve davvero cambiare.varsolo quando lavori in un progetto che lo richiede.
Applicata con coerenza, questa regola rende evidente a colpo d'occhio l'intenzione di ogni variabile: riassegnabile o no, confinata a questo blocco o a questa funzione.
MAX_RETRIES e users non cambiano mai: const. successful cresce: let. user è un nuovo binding a ogni iterazione: const. Leggendo dall'alto in basso capisci quali valori si muovono e quali no, senza eseguire il codice.
Prossimo passo: i tipi primitivi
Ora che sai dichiarare le variabili, la domanda successiva è che tipo di valori possono contenere. JavaScript ha un piccolo insieme di tipi primitivi (numeri, stringhe, booleani e pochi altri), ognuno con le sue particolarità. Sono l'argomento della prossima pagina.
Domande frequenti
Qual è la differenza tra let, const e var in JavaScript?
const e let hanno scope di blocco e sono arrivati con ES2015; var ha scope di funzione ed è più vecchio. I binding const non si possono riassegnare, quelli let sì. Inoltre var subisce l'hoisting e viene inizializzato a undefined, mentre let e const subiscono l'hoisting ma non sono utilizzabili finché la loro dichiarazione non viene eseguita (la temporal dead zone).
const rende un valore immutabile in JavaScript?
No. const impedisce solo di riassegnare il binding. Se il valore è un oggetto o un array, puoi comunque modificarne il contenuto: const user = {}; user.name = 'Ada' funziona senza problemi. Usa Object.freeze o una libreria se ti serve un'immutabilità vera.
Ha ancora senso usare var nel JavaScript moderno?
Quasi mai. let e const hanno regole di scope più chiare e intercettano più bug in fase di parsing. Gli unici posti in cui incontrerai var sono i progetti legacy e qualche script che deve girare in ambienti senza supporto a ES2015.