Scope e Hoisting in JavaScript: capire il ciclo di vita delle variabili locali

by Anastasia P.
SHARE
Scope e Hoisting in JavaScript: capire il ciclo di vita delle variabili locali
© Guida-HTML5.it

Introduzione

Quando si lavora con JavaScript, due concetti determinano spesso il comportamento “inaspettato” del codice: scope e hoisting. In pratica, lo scope stabilisce dove una variabile o una funzione è accessibile, mentre l’hoisting influenza quando una dichiarazione viene considerata dal motore JavaScript durante l’esecuzione.

In questo tutorial ci concentriamo su un sotto-argomento molto utile nella pratica: il ciclo di vita delle variabili locali dentro funzioni e blocchi. È un tema fondamentale per evitare bug difficili da individuare, soprattutto quando si mescolano var, let, const e funzioni annidate.

Capire bene questo argomento ti aiuta a leggere il codice con maggiore sicurezza, a prevenire errori di accesso a variabili non inizializzate e a scrivere funzioni più prevedibili e manutenibili.

Codice completo

// Esempio completo: scope locale, blocchi, funzioni e hoisting

function gestisciOrdine() {
  console.log("1) Inizio funzione");

  // Con var: la dichiarazione viene "alzata" (hoisted),
  // ma l´inizializzazione resta qui sotto.
  console.log("statoOrdine con var:", statoOrdine); // undefined
  var statoOrdine = "in preparazione";

  // Con let: la variabile esiste nello scope del blocco,
  // ma non è accessibile prima della dichiarazione.
  // console.log(cliente); // ReferenceError
  let cliente = "Luca";

  // const funziona come let per lo scope,
  // ma richiede anche un´inizializzazione immediata.
  const totale = 49.99;

  if (totale > 20) {
    // Nuovo scope di blocco
    let sconto = 5;
    const messaggio = "Sconto applicato";

    console.log("2) Dentro il blocco:", sconto, messaggio);
  }

  // console.log(sconto); // ReferenceError: sconto non è definita qui

  function calcolaRiepilogo() {
    // Questa funzione ha accesso alle variabili della funzione esterna
    console.log("3) Riepilogo per:", cliente);
    console.log("Stato:", statoOrdine);

    // Hoisting delle funzioni dichiarate
    console.log("Totale finale:", calcolaTotale(49.99, 5));

    function calcolaTotale(prezzo, sconto) {
      return prezzo - sconto;
    }
  }

  calcolaRiepilogo();

  console.log("4) Fine funzione");
}

gestisciOrdine();

// Differenza importante: var non rispetta lo scope di blocco
if (true) {
  var visibileOvunque = "Sono visibile fuori dal blocco";
  let visibileSoloNelBlocco = "Io resto nel blocco";
}

console.log(visibileOvunque); // OK
// console.log(visibileSoloNelBlocco); // ReferenceError

// Hoisting di una function declaration
saluta();

function saluta() {
  console.log("Ciao! Questa funzione è disponibile prima della sua definizione.");
}

// Hoisting di una function expression assegnata a var
// chiamaFunzione(); // TypeError: chiamaFunzione is not a function
var chiamaFunzione = function () {
  console.log("Funzione assegnata a var");
};

Spiegazione

Per capire davvero il codice, conviene separare i concetti in modo ordinato.

1) Cos’è lo scope

Lo scope è l’area del programma in cui un identificatore è visibile. In JavaScript, i principali scope che incontri sono:

  • Scope globale: variabili accessibili praticamente ovunque nel file.
  • Scope di funzione: variabili dichiarate dentro una funzione, visibili solo lì e nelle funzioni annidate.
  • Scope di blocco: variabili dichiarate dentro { }, ad esempio in if, for, while, visibili solo dentro quel blocco.

Nel codice, cliente, statoOrdine e totale vivono dentro gestisciOrdine(). Questo significa che non puoi usarle fuori dalla funzione.

2) Come funziona l’hoisting

L’hoisting è il comportamento con cui JavaScript “registra” le dichiarazioni prima di eseguire il codice riga per riga. Non significa che tutto venga inizializzato subito; cambia in base al tipo di dichiarazione.

  • var: la dichiarazione viene hoisted e inizializzata a undefined.
  • let e const: vengono hoisted, ma restano in una Temporal Dead Zone fino alla dichiarazione effettiva.
  • function declaration: viene hoisted con il suo corpo, quindi può essere chiamata prima della definizione nel file.

Nel nostro esempio, statoOrdine stampato prima della sua assegnazione con var produce undefined. Questo può nascondere bug, perché il programma non si blocca subito, ma usa un valore non ancora pronto.

3) Perché let e const sono più sicure

Se provi ad accedere a una variabile let o const prima della dichiarazione, JavaScript genera un errore. Questo è utile perché ti avvisa subito di un uso scorretto.

Nel codice, se decommenti console.log(cliente) prima di let cliente = "Luca", ottieni un ReferenceError. È un comportamento desiderabile: evita di lavorare con dati non ancora inizializzati.

4) Funzioni dentro funzioni

La funzione calcolaRiepilogo() mostra un aspetto importante dello scope lessicale: una funzione interna può leggere le variabili della funzione esterna. Per questo può usare cliente e statoOrdine senza riceverli come parametri.

Inoltre, la function declaration calcolaTotale() è disponibile prima della sua definizione effettiva nel codice, grazie all’hoisting delle funzioni dichiarate.

5) var non rispetta lo scope di blocco

Uno degli errori più comuni è usare var dentro un if pensando che resti confinato nel blocco. In realtà, var ha scope di funzione, non di blocco. Per questo visibileOvunque è accessibile anche fuori dall’if.

Al contrario, let visibileSoloNelBlocco non è disponibile all’esterno. Questo comportamento è molto più prevedibile e riduce il rischio di collisioni tra variabili.

Best practice

  • Preferisci let e const a var: migliorano la leggibilità e riducono gli errori legati allo scope di blocco.
  • Usa const per i valori che non devono essere riassegnati: comunica l’intento del codice in modo chiaro.
  • Dichiara le variabili prima di usarle: anche se alcune forme di hoisting lo permettono, è una pratica più pulita e meno ambigua.
  • Evita di affidarti all’hoisting per le funzioni: chiamare una funzione prima della sua definizione può funzionare, ma rende il codice meno immediato da leggere.
  • Limita la dimensione delle funzioni: più è piccola la funzione, più è semplice capire quali variabili appartengono al suo scope.
  • Non usare var nei nuovi progetti: è una fonte frequente di confusione, soprattutto nei blocchi e nei cicli.

Riepilogo

Scope e hoisting sono due pilastri del comportamento di JavaScript. Lo scope decide dove una variabile è visibile, mentre l’hoisting determina come le dichiarazioni vengono considerate prima dell’esecuzione.

Il punto chiave di questo tutorial è il seguente: le variabili locali hanno un ciclo di vita legato al loro scope. Dentro una funzione o un blocco, puoi accedere solo agli identificatori già dichiarati e disponibili in quel contesto. Con var puoi incontrare undefined a causa dell’hoisting; con let e const ottieni un errore più chiaro se provi a usarle troppo presto.

Se vuoi scrivere JavaScript solido, il consiglio è semplice: pensa sempre in termini di scope, usa dichiarazioni esplicite e non dare per scontato che “vedere” una variabile nel codice significhi poterla usare in qualsiasi punto.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Scope: documentazione chiara e completa sullo scope in JavaScript.
  • MDN Web Docs - Hoisting: approfondimento sul comportamento di hoisting per variabili e funzioni.
  • MDN Web Docs - let: dettagli su scope di blocco e Temporal Dead Zone.
  • MDN Web Docs - const: spiegazione dell’uso corretto di costanti e blocchi.
  • MDN Web Docs - var: utile per capire perché oggi è meglio evitarlo nei nuovi progetti.

SHARE