Introduzione
Quando si parla di Scope e Hoisting in JavaScript, molti pensano subito alle differenze tra var, let e const. Ma c’è un aspetto ancora più pratico e utile da conoscere: come JavaScript risolve i nomi delle variabili quando hai blocchi annidati, funzioni interne e variabili con lo stesso nome.
Questo tema è fondamentale perché in un progetto reale capita spesso di lavorare con codice complesso, callback, funzioni annidate o blocchi condizionali. Se non capisci bene come funziona lo scope, rischi di leggere o scrivere il valore sbagliato, oppure di creare bug difficili da individuare.
In questo tutorial vedremo un sotto-argomento molto concreto: la risoluzione delle variabili in presenza di omonimie tra scope interni ed esterni. Imparerai come JavaScript decide quale variabile usare, come l’hoisting influisce sulla lettura del codice e quali errori evitare per scrivere codice più sicuro e leggibile.
Codice completo
// Esempio completo: scope annidati, omonimie e hoisting
const ruolo = "Developer";
function mostraRuolo() {
console.log("1)", ruolo); // legge la variabile esterna
if (true) {
// Questa variabile nasconde quella esterna all´interno del blocco
let ruolo = "Team Lead";
console.log("2)", ruolo); // legge la variabile del blocco
}
console.log("3)", ruolo); // torna a leggere la variabile esterna
}
mostraRuolo();
function esempioHoisting() {
console.log("4)", messaggio); // undefined, non errore
var messaggio = "Ciao!";
console.log("5)", messaggio); // "Ciao!"
}
esempioHoisting();
function esempioTDZ() {
// console.log(nome); // ReferenceError: accesso prima della dichiarazione
let nome = "Alice";
console.log("6)", nome); // "Alice"
}
esempioTDZ();
function scopeAnnidato() {
let valore = 10;
function interno() {
let valore = 20;
console.log("7)", valore); // 20
}
interno();
console.log("8)", valore); // 10
}
scopeAnnidato();
function shadowingConVar() {
var x = "esterno";
if (true) {
var x = "interno";
console.log("9)", x); // "interno"
}
console.log("10)", x); // "interno"
}
shadowingConVar(); Spiegazione
Per capire questo esempio, partiamo da una regola chiave: JavaScript cerca una variabile prima nello scope più vicino, poi sale verso gli scope esterni. Questo processo si chiama scope chain.
1. Variabili con lo stesso nome: shadowing
Nel codice della funzione mostraRuolo, la variabile ruolo esiste sia all’esterno sia dentro il blocco if. Quando dichiari let ruolo = "Team Lead", stai creando una nuova variabile che nasconde quella esterna all’interno di quel blocco. Questo fenomeno si chiama shadowing.
- Fuori dal blocco:
ruolovale"Developer" - Dentro il blocco:
ruolovale"Team Lead" - Dopo il blocco: torna a valere
"Developer"
Questo comportamento è molto utile, ma può diventare pericoloso se usato senza attenzione, perché il nome uguale può far pensare che si stia usando la stessa variabile, mentre in realtà sono due variabili diverse.
2. Hoisting con var
Nella funzione esempioHoisting, la dichiarazione var messaggio viene “sollevata” all’inizio della funzione durante la fase di compilazione. Questo significa che JavaScript conosce già la variabile, ma non ancora il suo valore.
Per questo motivo, quando esegui:
console.log(messaggio); ottieni undefined e non un errore. La variabile esiste già, ma il suo valore non è stato assegnato ancora.
Questo è uno degli effetti più importanti dell’hoisting: con var puoi leggere una variabile prima della sua riga di dichiarazione, ma riceverai undefined. Non è sempre un vantaggio: spesso nasconde bug logici.
3. Hoisting con let e const
Nel caso di let e const, la situazione è diversa. Anche queste dichiarazioni vengono registrate nello scope, ma non sono accessibili prima della loro riga di inizializzazione. Questo intervallo si chiama Temporal Dead Zone (TDZ).
Per questo, se provi a leggere una variabile let prima della dichiarazione, JavaScript genera un ReferenceError.
Questo comportamento è più sicuro di var, perché ti impedisce di usare accidentalmente una variabile non ancora pronta.
4. Scope annidato e risoluzione dei nomi
Nella funzione scopeAnnidato, la variabile valore viene dichiarata due volte: una nello scope esterno della funzione e una nella funzione interna interno.
interno()stampa20perché usa la sua variabile locale- la funzione esterna stampa
10perché la sua variabile non viene modificata
Questo esempio mostra bene che ogni funzione crea un proprio scope lessicale. La variabile più vicina vince sempre nella risoluzione del nome.
5. var non rispetta lo scope di blocco
La funzione shadowingConVar mostra un comportamento tipico di var: non ha scope di blocco, ma solo scope di funzione. Quindi la dichiarazione dentro if non crea una nuova variabile, ma riusa la stessa variabile della funzione esterna.
Risultato:
- dentro il blocco,
xdiventa"interno" - fuori dal blocco,
xè ancora"interno"
Questo è uno dei motivi principali per cui oggi si preferiscono let e const: rendono lo scope più prevedibile.
Best practice
Quando lavori con scope e hoisting, alcune abitudini ti aiutano a evitare errori e a rendere il codice più manutenibile.
- Preferisci
constper i valori che non devono cambiare. Riduce gli errori e comunica meglio l’intenzione. - Usa
letsolo quando il valore deve essere riassegnato. - Evita
varnei nuovi progetti, perché il suo comportamento con hoisting e scope di funzione è meno intuitivo. - Non riutilizzare lo stesso nome in scope annidati senza motivo. Lo shadowing è legale, ma può confondere chi legge il codice.
- Dichiara le variabili prima di usarle. Anche se alcune forme di hoisting lo permettono, scrivere codice in ordine lineare migliora la leggibilità.
- Attenzione ai blocchi: con
leteconstogni blocco crea uno scope separato, quindi una variabile definita dentro unifnon esiste fuori da esso.
Un buon criterio pratico è questo: se leggendo il codice devi fermarti a chiederti “quale variabile sta usando qui?” probabilmente il nome è troppo ambiguo.
Riepilogo
In questo tutorial abbiamo visto un aspetto molto concreto di Scope e Hoisting in JavaScript: come il motore risolve variabili con lo stesso nome in scope diversi.
- JavaScript cerca prima nello scope più vicino e poi risale verso quelli esterni.
- Se una variabile interna ha lo stesso nome di una esterna, la interna la nasconde: è lo shadowing.
varha hoisting visibile comeundefinede non rispetta lo scope di blocco.leteconstsono bloccate dalla Temporal Dead Zone prima della dichiarazione.- Usare nomi chiari e scope ben separati rende il codice più sicuro e facile da mantenere.
Se vuoi scrivere JavaScript moderno e affidabile, devi pensare allo scope non solo come a “dove vive una variabile”, ma come a come JavaScript decide quale variabile usare in ogni punto del codice.
Approfondisci con risorse ufficiali
- MDN Web Docs - Scope: documentazione chiara e completa sul concetto di scope in JavaScript.
- MDN Web Docs - Hoisting: spiegazione dettagliata del comportamento di hoisting per variabili e funzioni.
- MDN Web Docs - let: utile per capire scope di blocco e Temporal Dead Zone.
- MDN Web Docs - const: approfondimento sulle costanti e sul loro comportamento nello scope.
- ECMAScript Language Specification: la specifica ufficiale per chi vuole andare oltre la pratica e studiare il comportamento standard del linguaggio.
