Aggiornato: Ottobre 2026
Ancora confuso su quando usare var, let o const? Smetti di tirare a indovinare. Scopri la guida completa su Scope, Hoisting e Temporal Dead Zone per scrivere JavaScript moderno, sicuro e privo di bug.
Se hai iniziato a programmare con JavaScript prima del 2015, il tuo vocabolario per le variabili si limitava a una sola parola: var. Tuttavia, padroneggiare la triade var, let e const è oggi la competenza fondamentale per ogni sviluppatore moderno.
JavaScript è un linguaggio di programmazione dinamico e flessibile, e proprio questa flessibilità ha storicamente portato a bug difficili da individuare. L’introduzione di ES6 (ECMAScript 2015) non ha portato solo “nuova sintassi”, ma ha risolto problemi architetturali storici del linguaggio. Comprendere le differenze non è solo una questione di stile; è la base per scrivere codice pulito (Clean Code), sicuro e performante.
In questa guida completa, analizzeremo non solo il come, ma il perché tecnico dietro Scope, Hoisting e la gestione della memoria, con un capitolo dedicato a ciascun concetto.
In questa guida:
- Tabella riassuntiva: le differenze
- Scope: global, function e block scope, scope chain
- Hoisting e Temporal Dead Zone
- Mutabilità e const
- Best practice
- Errori comuni
- FAQ
Tabella Riassuntiva Rapida: Le Differenze
Prima di scendere nei dettagli tecnici, ecco la mappa mentale fondamentale:
| Caratteristica | var (Legacy) | let (Moderno) | const (Moderno) |
|---|---|---|---|
| Scope | Function Scope 🏠 | Block Scope 📦 | Block Scope 📦 |
| Riassegnabile? | ✅ Sì | ✅ Sì | ❌ No |
| Hoisting | Sì (inizializzato undefined) | Sì (in TDZ 💀) | Sì (in TDZ 💀) |
| Global Pollution | Sì (va su window) | No | No |
1. La Battaglia dello Scope: Dove vive la tua variabile?
Lo scope definisce l’area del codice in cui una variabile è “visibile”. Questa è la distinzione più critica e il motivo principale per cui var è stata quasi del tutto abbandonata.
In termini semplici, lo Scope (o ambito di visibilità) definisce dove puoi accedere a una specifica variabile o funzione all’interno del tuo codice.
Immagina lo Scope come un sistema di sicurezza a livelli:
- Determina chi può vedere le tue variabili.
- Decide quali variabili sono disponibili in un preciso momento dell’esecuzione.
Esistono tre tipi principali di Scope che devi conoscere per padroneggiare lo sviluppo moderno.
1.1 Global Scope (ambito globale)
Una variabile dichiarata fuori da qualsiasi funzione o blocco appartiene al Global Scope.
Caratteristiche:
- È accessibile da qualsiasi punto del tuo codice.
- Vive per l’intera durata della sessione della pagina web.
// Variabile nel Global Scope
const nomeBlog = "fabiogulotta.it";
function stampaNome() {
// Posso leggerla qui dentro perché è globale
console.log("Benvenuti su " + nomeBlog);
}
stampaNome(); // Output: Benvenuti su fabiogulotta.it
Attenzione: L’uso eccessivo del Global Scope è considerato una bad practice. Se troppe parti del tuo codice possono modificare la stessa variabile globale, il rischio di bug imprevedibili (side effects) aumenta drasticamente.
1.2 var: Scope di Funzione (Il vecchio approccio)
Le variabili dichiarate con var ignorano i blocchi di codice come if o for. Esse “vedono” solo i confini della funzione in cui si trovano. Se non sono in una funzione, sono globali.
function usaVar() {
if (true) {
var messaggio = "Sono accessibile ovunque nella funzione!";
}
// Nessun errore: var scavalca il blocco if!
console.log(messaggio);
}Fino a qualche anno fa (prima di ES6), questo era il modo principale per creare variabili private. Le variabili dichiarate all’interno di una funzione sono visibili solo all’interno di quella funzione.
function calcolaIva() {
var aliquota = 0.22; // Visibile solo qui dentro
console.log(aliquota);
}
calcolaIva(); // Output: 0.22
// Errore: aliquota is not defined
console.log(aliquota);
Questo meccanismo è fondamentale per il principio del “Least Privilege” (Minimo Privilegio): esponi solo ciò che è strettamente necessario.
1.3 let e const: Scope di Blocco (Il recinto sicuro)
Con let e const, le variabili nascono e muoiono all’interno delle parentesi graffe {} in cui sono dichiarate. Questo comportamento (Block Scope) previene fughe di dati involontarie.
function usaLet() {
if (true) {
let segreto = "Non puoi leggermi fuori";
}
// ❌ Errore: segreto is not defined
// console.log(segreto);
}Un “blocco” è qualsiasi porzione di codice racchiusa tra parentesi graffe {} (come in un if, un ciclo for, ecc).
1.4 Il Bug del Loop: Perché var ti tradisce nell’asincrono
Questa è la classica domanda da colloquio tecnico. La differenza di scope diventa critica quando usi callback asincrone e closure (come setTimeout) dentro un ciclo.
Il problema con var:
Poiché var non ha scope di blocco, la variabile i è condivisa tra tutte le iterazioni. Quando il timer scatta (dopo 1 secondo), il ciclo è già finito e tutte le callback leggono lo stesso valore finale.
// ❌ Esempio errato con VAR
for (var i = 0; i < 3; i++) {
// 'i' è la stessa variabile per tutti!
setTimeout(() => console.log("Var: " + i), 1000);
}
// Output disastroso: "Var: 3", "Var: 3", "Var: 3"La soluzione con let:
Grazie allo scope di blocco, let crea una nuova associazione per la variabile in ogni singola iterazione. Ogni timer “ricorda” il valore esatto di quel giro.
// ✅ Esempio corretto con LET
for (let i = 0; i < 3; i++) {
// Una nuova 'i' viene creata per ogni iterazione
setTimeout(() => console.log("Let: " + i), 1000);
}
// Output corretto: "Let: 0", "Let: 1", "Let: 2"1.5 La Scope Chain (catena degli scope)
Cosa succede se JavaScript non trova una variabile nello scope corrente? Semplice: guarda “fuori”.
Questo meccanismo si chiama Scope Chain. Il motore JavaScript cerca la variabile nel livello corrente; se non la trova, sale al livello genitore (outer scope) e continua così fino a raggiungere il Global Scope. Se non la trova nemmeno lì, lancia un errore.
const globale = "Sono Globale";
function esterna() {
const localeEsterna = "Sono nella funzione esterna";
function interna() {
const localeInterna = "Sono nella funzione interna";
// Qui posso accedere a tutte!
console.log(localeInterna); // Trovata qui
console.log(localeEsterna); // Trovata nello scope genitore
console.log(globale); // Trovata nel global scope
}
interna();
}
Nota importante: La catena funziona solo verso l’esterno (o verso l’alto). Lo scope genitore non può mai vedere le variabili definite all’interno dei suoi figli.
2. Hoisting e Temporal Dead Zone (TDZ)
L’Hoisting (sollevamento) è il comportamento del motore JavaScript che “sposta” le dichiarazioni in cima al loro scope. Per una definizione tecnica formale puoi consultare il glossario MDN sull’Hoisting. Ma c’è una sfumatura vitale da comprendere.
2.1 Dichiarazione vs. inizializzazione
È essenziale distinguere tra i due stadi per capire l’hoisting:
- Dichiarazione: Il momento in cui annunci l’esistenza della variabile (
var nome;). Questo è ciò che viene sollevato. - Inizializzazione: Il momento in cui assegni un valore alla variabile (
nome = "Alice";). Questo non viene sollevato.
L’hoisting è un concetto applicato durante la fase di compilazione del codice, prima che la singola riga di codice venga effettivamente eseguita.
2.2 Var: Hoisting con inizializzazione
Con var, la variabile viene sollevata e inizializzata subito a undefined. Questo permetteva codice disordinato e buggato:
console.log(auto); // Output: undefined
var auto = "Fiat";
console.log(auto); // Output: Fiat
/* Ciò che JavaScript vede in esecuzione:
var auto; // Dichiarazione sollevata e inizializzata a undefined
console.log(auto);
auto = "Fiat";
console.log(auto);
*/
2.3 Let/Const: La Temporal Dead Zone
Molti pensano che let e const non subiscano hoisting. Falso. Vengono sollevate, ma non inizializzate.
Il periodo di tempo tra l’ingresso nello scope e la dichiarazione effettiva si chiama Temporal Dead Zone (TDZ). Se provi a toccare la variabile nella TDZ, JavaScript lancia un errore, proteggendoti.
{
// 💀 INIZIO TDZ
// console.log(piatto); // ReferenceError: Cannot access before initialization
let piatto = "Pasta"; // 🏁 FINE TDZ - La variabile ora è sicura
console.log(piatto); // "Pasta"
}2.4 Hoisting delle funzioni
L’Hoisting si applica in modo potente anche alle funzioni dichiarate (quelle definite con la sintassi function nome() {}):
- Sollevamento Totale: L’intera funzione, sia la dichiarazione che la definizione, viene sollevata.
- Il Vantaggio: Puoi chiamare la funzione prima di dove appare nel tuo file di codice.
Esempio pratico con una funzione
saluta("Fabio"); // OK: Output: Ciao, Fabio!
function saluta(nome) {
console.log("Ciao, " + nome + "!");
}
Attenzione: Le Funzioni Espressione (funzioni assegnate a una variabile, ad es. con const saluta = function() {} o const saluta = () => {}) seguono le regole di hoisting della variabile (let o const) e non possono essere chiamate prima della loro dichiarazione.
3. Mutabilità e Gestione della Memoria
Quando usare let e quando const? La risposta sta nella mutabilità.
3.1 Const: un binding di sola lettura, non un valore immutabile
const è una parola chiave riservata di JavaScript che crea un legame (binding) di sola lettura. Una volta assegnato un valore, quel binding non può essere riassegnato. Questo può essere paragonato a const int nei linguaggi a tipizzazione statica come C o Java, con una differenza fondamentale: in JavaScript il valore puntato può comunque mutare se è un oggetto. Anche MDN raccomanda di usare const per default ogni volta che una variabile non viene riassegnata.
const TASSA = 22;
// TASSA = 25; // TypeError: Assignment to constant variable.3.2 Il trucco degli Oggetti (Reference vs Value)
Attenzione: const blocca l’assegnazione, ma non congela il valore se questo è un oggetto o un array. In termini di memoria: protegge l’indirizzo di casa, ma non ti impedisce di cambiare l’arredamento interno.
const utente = { nome: "Mario" };
// ❌ ERRORE: Non puoi cambiare il riferimento (l'indirizzo)
// utente = { nome: "Luigi" };
// ✅ OK: Puoi modificare il contenuto all'interno!
utente.nome = "Luigi";
console.log(utente.nome); // "Luigi"Se hai bisogno di un oggetto davvero immutabile, puoi usare Object.freeze():
const utente = Object.freeze({ nome: "Mario" });
utente.nome = "Luigi"; // Silenziosamente ignorato in strict mode
console.log(utente.nome); // "Mario"4. Best Practices per JavaScript moderno
Come scrivere codice pulito oggi? Segui questa semplice gerarchia:
Default: const. Usa sempre const come prima scelta. Rende il codice più leggibile perché comunica che quel valore non dovrebbe cambiare. In React, ad esempio, tutti i hook devono essere dichiarati con const (const [stato, setStato] = useState(null)).
Opzione B: let. Usa let solo se sai per certo che il valore dovrà cambiare — contatori di loop, accumulatori, variabili che cambiano a seguito di una condizione.
Mai: var. Non c’è quasi nessun motivo valido per usare var nel codice moderno. L’unica eccezione possibile è il lavoro su codice legacy molto datato che non può essere refactorizzato.
5. Errori Comuni da Evitare
Anche sviluppatori esperti cadono in questi pattern. Ecco i più frequenti con la soluzione corretta.
Errore 1: Usare var in un ciclo con callback asincrone
Come visto nella sezione 1.4, var in un loop asincrono produce valori inaspettati. La soluzione è sempre let.
Errore 2: Dichiarare variabili con var nello scope globale
// ❌ Sbagliato — inquina l'oggetto window
var appName = "MyApp";
console.log(window.appName); // "MyApp" — pericoloso!
// ✅ Corretto — rimane nello scope del modulo
const appName = "MyApp";
console.log(window.appName); // undefined — sicuroErrore 3: Confondere const con immutabilità totale
// ❌ Pensiero sbagliato
const config = { debug: false };
// "Non posso cambiare nulla perché è const"
// ✅ Realtà: il contenuto dell'oggetto può essere modificato
config.debug = true; // Funziona perfettamente!
// Solo config = { ... } darebbe erroreErrore 4: Accedere a una variabile let/const prima della dichiarazione
// ❌ Sbagliato — TDZ attiva
console.log(valore); // ReferenceError
let valore = 42;
// ✅ Corretto — dichiara sempre prima di usare
let valore = 42;
console.log(valore); // 426. FAQ — Domande Frequenti
Quando usare let invece di const?
Usa let quando sai che il valore della variabile dovrà cambiare durante l’esecuzione del programma: contatori, accumulatori, variabili aggiornate in base a condizioni. In tutti gli altri casi preferisci const.
var è deprecato in JavaScript?
Formalmente no: var è ancora una parola chiave valida nel linguaggio di programmazione JavaScript e non è stata rimossa. In pratica però è considerata legacy da tutti i moderni standard di scrittura del codice (ESLint, Airbnb Style Guide, Google Style Guide). Non deve essere usata nel codice moderno.
const rende un oggetto completamente immutabile?
No. const crea un binding di sola lettura: non puoi riassegnare la variabile, ma il contenuto dell’oggetto o dell’array a cui punta può essere modificato. Per un’immutabilità totale devi usare Object.freeze().
Qual è la differenza tra scope di funzione e scope di blocco?
Lo scope di funzione (usato da var) rende la variabile visibile in tutta la funzione, indipendentemente da dove viene dichiarata. Lo scope di blocco (usato da let e const) limita la visibilità alle sole parentesi graffe {} in cui la variabile è stata dichiarata.
Cosa succede se uso var fuori da qualsiasi funzione?
La variabile diventa globale e viene aggiunta direttamente all’oggetto window del browser. Questo può causare collisioni con librerie esterne o altri script e viene definito “global pollution”. Con let e const dichiarate allo stesso livello, questo non accade.
Cos’è l’hoisting in JavaScript?
È il processo per cui il motore sposta logicamente le dichiarazioni di funzioni e variabili in cima al loro scope prima che il codice venga eseguito. Le inizializzazioni restano dove sono: con var la variabile vale undefined fino all’assegnazione, con let e const resta nella Temporal Dead Zone e un accesso anticipato genera un ReferenceError.
Le funzioni subiscono hoisting?
Sì. Le funzioni dichiarate con function nome() {} vengono sollevate per intero, quindi puoi chiamarle prima di dove appaiono nel file. Le funzioni espressione e le arrow function assegnate con const o let seguono invece le regole della variabile e non possono essere chiamate prima della dichiarazione.
Cos’è la scope chain?
È il meccanismo con cui JavaScript cerca una variabile: parte dallo scope corrente e, se non la trova, sale allo scope genitore fino al Global Scope. Se non la trova nemmeno lì, lancia un ReferenceError. La ricerca va solo verso l’esterno: uno scope genitore non vede mai le variabili dei suoi figli.
Conclusione
Passare da var a let e const è stato uno dei più grandi salti di qualità nella storia di JavaScript come linguaggio di programmazione. Hai guadagnato scope prevedibili, sicurezza contro gli errori di digitazione grazie alla TDZ e una gestione più chiara della memoria.
Per scrivere codice pulito e manutenibile, tieni a mente queste tre regole pratiche:
- Minimizza l’uso del Global Scope: Meno variabili globali hai, meno probabilità hai che entrino in conflitto.
- Usa
constelet: Dimenticavar. Il Block Scope rende il flusso del codice più prevedibile e facile da leggere. - Dichiara le variabili vicino a dove le usi: Grazie al Block Scope, è buona pratica definire le variabili nel contesto più ristretto possibile.
Ora che sai come dichiarare le variabili nel modo corretto, puoi applicare subito questi concetti in un contesto reale: la guida su Clean Code in JavaScript ti mostrerà pattern pratici per scrivere codice professionale. Per il quadro completo delle novità introdotte da ES6 in poi, leggi la guida a JavaScript moderno.








