[Medium] 📄 Hoisting
1. Cos'è l'Hoisting?
L'esecuzione di JavaScript può essere vista come due fasi: creazione ed esecuzione.
var name = 'Pitt';
console.log(name); // stampa Pitt
Con l'hoisting, il motore gestisce concettualmente prima la dichiarazione e poi l'assegnazione:
// creazione
var name;
// esecuzione
name = 'Pitt';
console.log(name);
Le funzioni si comportano diversamente dalle variabili. Una dichiarazione di funzione viene vincolata durante la fase di creazione:
getName();
function getName() {
console.log('string'); // stampa string
}
Funziona perché la dichiarazione di funzione viene sollevata (hoisted) prima delle chiamate in fase di esecuzione:
// creazione
function getName() {
console.log('string');
}
// esecuzione
getName();
Nota: con le espressioni di funzione, l'ordine di dichiarazione e assegnazione è ancora importante.
Nella fase di creazione, le dichiarazioni di funzione hanno priorità più alta rispetto alle dichiarazioni di variabili.
Corretto
name = 'Yumy';
console.log(name); // stampa Yumy
var name;
// --- Equivale a ---
// creazione
var name;
// esecuzione
name = 'Yumy';
console.log(name); // stampa Yumy
Sbagliato
console.log(name); // stampa undefined
var name = 'Jane';
// --- Equivale a ---
// creazione
var name;
// esecuzione
console.log(name); // stampa undefined, perché l'assegnazione non è ancora avvenuta
name = 'Pitt';
2. Cosa viene stampato per name?
whoseName();
function whoseName() {
if (name) {
name = 'Nini';
}
}
var name = 'Pitt';
console.log(name);
Risposta
// creazione
function whoseName() {
if (name) {
name = 'Nini';
}
}
var name;
// esecuzione
whoseName();
name = 'Pitt';
console.log(name); // stampa Pitt
Dentro whoseName(), name inizia come undefined, quindi il ramo if (name) non viene eseguito.
Dopo, name viene assegnato 'Pitt', quindi l'output finale è comunque Pitt.
3. Dichiarazione di funzione vs dichiarazione di variabile: priorità dell'Hoisting
Domanda: funzione e variabile con lo stesso nome
Prevedi l'output di questo codice:
console.log(foo);
var foo = '1';
function foo() {}
Risposte errate comuni
Molte persone pensano che:
- Stamperà
undefined(supponendo chevarvenga sollevato per primo) - Stamperà
'1'(supponendo che l'assegnazione sia già avvenuta) - Lancerà un errore (supponendo un conflitto per stesso nome)
Output effettivo
[Function: foo]
Perché?
Questa domanda testa la regola di priorità dell'hoisting:
Priorità dell'hoisting: dichiarazione di funzione > dichiarazione di variabile
// Codice originale
console.log(foo);
var foo = '1';
function foo() {}
// Equivalente dopo l'hoisting
// Fase 1: creazione (hoisting)
function foo() {} // 1. la dichiarazione di funzione viene sollevata per prima
var foo; // 2. la dichiarazione di variabile viene sollevata (non sovrascrive la funzione)
// Fase 2: esecuzione
console.log(foo); // foo è una funzione qui
foo = '1'; // 3. l'assegnazione sovrascrive la funzione
Concetti chiave
1. Le dichiarazioni di funzione vengono completamente sollevate
console.log(myFunc); // [Function: myFunc]
function myFunc() {
return 'Hello';
}
2. var solleva solo la dichiarazione, non l'assegnazione
console.log(myVar); // undefined
var myVar = 'Hello';
3. Quando dichiarazioni di funzione e variabile condividono un nome
// Ordine di hoisting
function foo() {} // la funzione viene sollevata e inizializzata per prima
var foo; // dichiarazione sollevata, ma non sovrascrive la funzione
// Quindi foo è ancora una funzione
console.log(foo); // [Function: foo]
Flusso di esecuzione completo
// Codice originale
console.log(foo); // ?
var foo = '1';
function foo() {}
console.log(foo); // ?
// ======== Equivalente ========
// Fase di creazione (hoisting)
function foo() {} // 1️⃣ la dichiarazione di funzione viene completamente sollevata
var foo; // 2️⃣ la dichiarazione di variabile viene sollevata ma non sostituisce la funzione
// Fase di esecuzione
console.log(foo); // [Function: foo]
foo = '1'; // 3️⃣ l'assegnazione ora sovrascrive la funzione
console.log(foo); // '1'
Domande estese
Domanda A: l'ordine cambia il risultato?
console.log(foo); // ?
function foo() {}
var foo = '1';
console.log(foo); // ?
Risposta:
[Function: foo] // primo output
'1' // secondo output
Motivo: l'ordine nel sorgente non cambia la priorità dell'hoisting. La dichiarazione di funzione vince comunque sulla dichiarazione di variabile nella fase di creazione.
Domanda B: funzioni multiple con lo stesso nome
console.log(foo); // ?
function foo() {
return 1;
}
var foo = '1';
function foo() {
return 2;
}
console.log(foo); // ?
Risposta:
[Function: foo] // primo output (la dichiarazione di funzione successiva sovrascrive la precedente)
'1' // secondo output (l'assegnazione sovrascrive la funzione)
Motivo:
// Dopo l'hoisting
function foo() {
return 1;
} // prima funzione
function foo() {
return 2;
} // la seconda funzione sovrascrive la prima
var foo; // solo dichiarazione (non sovrascrive la funzione)
console.log(foo); // funzione che restituisce 2
foo = '1'; // l'assegnazione sovrascrive la funzione
console.log(foo); // '1'
Domanda C: espressione di funzione vs dichiarazione di funzione
console.log(foo); // ?
console.log(bar); // ?
var foo = function () {
return 1;
};
function bar() {
return 2;
}
Risposta:
undefined // foo è undefined
[Function: bar] // bar è una funzione
Motivo:
// Dopo l'hoisting
var foo; // la dichiarazione di variabile viene sollevata (il corpo della funzione no)
function bar() {
return 2;
} // la dichiarazione di funzione viene completamente sollevata
console.log(foo); // undefined
console.log(bar); // funzione
foo = function () {
return 1;
}; // assegnazione a runtime
Differenza chiave:
- Dichiarazione di funzione:
function foo() {}-> completamente sollevata (incluso il corpo) - Espressione di funzione:
var foo = function() {}-> solo il nome della variabile viene sollevato
let/const evitano questo pattern
// ❌ `var` ha insidie di hoisting
console.log(foo); // undefined
var foo = '1';
// ✅ let/const sono nella TDZ prima dell'inizializzazione
console.log(bar); // ReferenceError
let bar = '1';
// ✅ usare lo stesso nome con funzione e let/const lancia un errore
function baz() {}
let baz = '1'; // SyntaxError: Identifier 'baz' has already been declared
Riepilogo delle priorità dell'Hoisting
Priorità dell'hoisting (alta -> bassa):
1. Dichiarazione di funzione
- function foo() {} ✅ completamente sollevata
- priorità più alta
2. Dichiarazione di variabile
- var foo ⚠️ solo dichiarazione (nessuna assegnazione)
- non sovrascrive una dichiarazione di funzione esistente
3. Assegnazione di variabile
- foo = '1' ✅ può sovrascrivere la funzione
- avviene solo nella fase di esecuzione
4. Espressione di funzione
- var foo = function() {} ⚠️ trattata come assegnazione
- solo il nome della variabile viene sollevato
Focus nei colloqui (Interview Focus)
Quando rispondi a questo tipo di domande, puoi seguire questa struttura:
- Spiega l'hoisting come due fasi: creazione ed esecuzione.
- Enfatizza la priorità: dichiarazione di funzione > dichiarazione di variabile.
- Riscrivi il codice nella forma dopo l'hoisting per mostrare il ragionamento.
- Menziona le best practice: preferisci
let/const, ed evita i pattern confusi divar.
Risposta esempio per un colloquio:
Questa domanda riguarda la priorità dell'hoisting. In JavaScript, le dichiarazioni di funzione vengono sollevate prima delle dichiarazioni di variabili.
Il motore attraversa due fasi:
- Fase di creazione:
function foo() {}viene sollevata per prima, poivar fooviene sollevata senza sovrascrivere la funzione.- Fase di esecuzione:
console.log(foo)stampa la funzione, e solo successivamentefoo = '1'la sovrascrive.In pratica, usa
let/constinvece divarper evitare questa confusione.