Hoisting JavaScript

Edoardo Midali
Edoardo Midali

L’hoisting è un meccanismo di JavaScript che “solleva” le dichiarazioni di variabili e funzioni all’inizio del loro scope durante la fase di compilazione. Questo significa che puoi utilizzare variabili e funzioni prima di averle dichiarate nel codice, ma il comportamento varia significativamente a seconda del tipo di dichiarazione utilizzata.

Come Funziona l’Hoisting

Durante l’esecuzione del codice JavaScript, il motore attraversa due fasi principali: la fase di compilazione e la fase di esecuzione. Durante la compilazione, tutte le dichiarazioni vengono identificate e “sollevate” all’inizio del loro scope, ma solo le dichiarazioni, non le inizializzazioni.

// Quello che scriviamo
console.log(messaggio); // undefined (non errore!)
var messaggio = "Ciao mondo";

// Come JavaScript lo interpreta internamente
var messaggio; // Dichiarazione sollevata
console.log(messaggio); // undefined
messaggio = "Ciao mondo"; // Inizializzazione rimane qui

È importante capire che viene sollevata solo la dichiarazione, non l’assegnazione del valore. Questo spiega perché messaggio risulta undefined invece di generare un errore di riferimento.

Hoisting con var

Le variabili dichiarate con var vengono completamente sollevate e inizializzate con undefined:

function esempiVar() {
  console.log(nome); // undefined
  console.log(età); // undefined

  if (true) {
    var nome = "Mario";
    var età = 30;
  }

  console.log(nome); // "Mario"
  console.log(età); // 30
}

// Interpretazione interna di JavaScript
function esempiVar() {
  var nome; // = undefined
  var età; // = undefined

  console.log(nome); // undefined
  console.log(età); // undefined

  if (true) {
    nome = "Mario";
    età = 30;
  }

  console.log(nome); // "Mario"
  console.log(età); // 30
}

Le variabili var hanno function scope, non block scope, quindi sono accessibili in tutta la funzione indipendentemente da dove sono dichiarate.

Hoisting con let e const

Le variabili dichiarate con let e const vengono tecnicamente sollevate, ma rimangono in una “zona morta temporale” (Temporal Dead Zone) fino alla loro dichiarazione:

function esempiLetConst() {
  console.log(nome); // ReferenceError: Cannot access 'nome' before initialization
  console.log(età); // ReferenceError: Cannot access 'età' before initialization

  let nome = "Mario";
  const età = 30;

  console.log(nome); // "Mario"
  console.log(età); // 30
}

La Temporal Dead Zone è il periodo tra l’inizio dello scope e il punto in cui la variabile viene dichiarata. Durante questo periodo, accedere alla variabile genera un ReferenceError.

Block Scope vs Function Scope

function confrontoScope() {
  console.log(varVariable); // undefined (hoisting)
  // console.log(letVariable); // ReferenceError

  if (true) {
    var varVariable = "var funziona";
    let letVariable = "let funziona";

    console.log(varVariable); // "var funziona"
    console.log(letVariable); // "let funziona"
  }

  console.log(varVariable); // "var funziona" (function scope)
  // console.log(letVariable); // ReferenceError (block scope)
}

Le variabili let e const hanno block scope e non sono accessibili fuori dal blocco in cui sono dichiarate.

Hoisting delle Funzioni

Function Declarations

Le dichiarazioni di funzione vengono completamente sollevate, incluso il corpo della funzione:

// Funziona perfettamente
saluta(); // "Ciao!"

function saluta() {
  console.log("Ciao!");
}

// JavaScript lo interpreta come:
function saluta() {
  console.log("Ciao!");
}

saluta(); // "Ciao!"

Questo permette di chiamare funzioni prima di dichiararle, una caratteristica utile per l’organizzazione del codice.

Function Expressions

Le espressioni di funzione seguono le regole delle variabili:

// Con var
console.log(saluta1); // undefined
saluta1(); // TypeError: saluta1 is not a function

var saluta1 = function () {
  console.log("Ciao da espressione!");
};

// Con let/const
console.log(saluta2); // ReferenceError
const saluta2 = function () {
  console.log("Ciao da const!");
};

Arrow Functions

Le arrow function seguono le stesse regole delle espressioni di funzione:

console.log(freccia); // ReferenceError (se let/const) o undefined (se var)

const freccia = () => {
  console.log("Arrow function");
};

Esempi Pratici e Problemi Comuni

Problema Classico con var nei Loop

// Comportamento imprevisto
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Stampa 3, 3, 3
  }, 100);
}

// Soluzione con let
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Stampa 0, 1, 2
  }, 100);
}

Con var, la variabile i viene sollevata fuori dal loop e condivisa tra tutte le iterazioni. Con let, ogni iterazione ha la sua copia di i.

Dichiarazioni Multiple

// Comportamento con var
var nome = "Primo";
console.log(nome); // "Primo"

var nome = "Secondo"; // Ridichiarazione permessa
console.log(nome); // "Secondo"

// Comportamento con let/const
let età = 25;
console.log(età); // 25

// let età = 30; // SyntaxError: Identifier 'età' has already been declared

Temporal Dead Zone in Dettaglio

La Temporal Dead Zone è una protezione che previene l’uso di variabili prima della loro inizializzazione:

function temporalDeadZone() {
  // TDZ inizia qui per 'temp'

  console.log(typeof temp); // ReferenceError, non "undefined"

  let temp = "valore"; // TDZ finisce qui

  console.log(temp); // "valore"
}

// Caso particolare con typeof
console.log(typeof variabileInesistente); // "undefined"
console.log(typeof variabileLet); // ReferenceError
let variabileLet = "test";

Best Practices

Dichiara sempre le variabili prima dell’uso: Anche se l’hoisting lo permette, è una pratica migliore dichiarare le variabili all’inizio del loro scope.

Preferisci let e const a var: Offrono un comportamento più prevedibile e aiutano a prevenire errori comuni.

Usa const per default: Se il valore non cambia, usa const. Usa let solo quando devi riassegnare la variabile.

Attenzione alle funzioni nei blocchi: Le dichiarazioni di funzione all’interno di blocchi hanno comportamenti inconsistenti tra browser.

// Best practice: dichiarazioni all'inizio
function buonaPratica() {
  let nome;
  let età;
  const configurazione = { tema: "scuro" };

  // Logica della funzione...
  nome = "Mario";
  età = 30;

  return { nome, età, configurazione };
}

Evita l’hoisting intenzionale: Non fare affidamento sull’hoisting per chiamare funzioni prima della loro dichiarazione, rende il codice meno leggibile.

L’hoisting è un concetto fondamentale per comprendere come JavaScript esegue il codice. Mentre può essere utile in alcuni casi, è generalmente meglio scrivere codice che non faccia affidamento su questo meccanismo per mantenere la chiarezza e prevenire errori difficili da debuggare.