Hoisting: cómo JavaScript mueve las declaraciones
JavaScript mueve las declaraciones al inicio de su scope antes de ejecutar el código. Entender hoisting explica comportamientos que parecen magia — o bugs misteriosos.
¿Qué es el hoisting?
Antes de ejecutar tu código, el motor de JavaScript hace una pasada y mueve las declaraciones al inicio de su scope. No el código físicamente — sino que las registra en memoria primero.
// Esto funciona aunque saludar() está declarada abajo
console.log(saludar("Ana")); // "Hola, Ana"
function saludar(nombre) {
return `Hola, ${nombre}`;
}
// Así lo ve JavaScript internamente (conceptualmente):
// function saludar(nombre) { ... } ← sube completa
// console.log(saludar("Ana"));// TypeScript también aplica hoisting (compila a JS)
console.log(saludar("Ana")); // "Hola, Ana"
function saludar(nombre: string): string {
return `Hola, ${nombre}`;
}
// Nota: TypeScript puede dar error de análisis estático
// en algunos editores, pero el runtime lo permiteHola, AnaEl código fuente no cambia. El motor JavaScript simplemente registra las declaraciones en memoria durante la fase de compilación, antes de la ejecución línea por línea.
Hoisting de var — declaración sube, asignación no
Con var, la declaración sube al inicio del scope de función, pero la asignación se queda en su lugar. El resultado: la variable existe pero vale undefined antes de su asignación.
// Lo que escribes:
console.log(ciudad); // undefined (no ReferenceError)
var ciudad = "Guadalajara";
console.log(ciudad); // "Guadalajara"
// Lo que JavaScript "ve":
// var ciudad; ← declaración sube
// console.log(ciudad); // undefined
// ciudad = "Guadalajara"; ← asignación se queda
// console.log(ciudad); // "Guadalajara"
function ejemplo() {
console.log(x); // undefined
var x = 10;
console.log(x); // 10
}
ejemplo();// TypeScript permite var pero el comportamiento es igual
function ejemplo(): void {
console.log(x); // undefined en runtime
var x: number = 10;
console.log(x); // 10
}
ejemplo();
// TypeScript recomienda let/const precisamente
// para evitar este comportamiento confusoundefined
Guerreros
undefined
10Hoisting de funciones — suben completas
Las declaraciones de función (function declarations) son el caso más completo de hoisting: la función entera — nombre y cuerpo — sube al inicio del scope.
// ✅ Declaración de función — hoisting completo
console.log(sumar(3, 4)); // 7
function sumar(a, b) {
return a + b;
}
// ❌ Expresión de función — NO hace hoisting del cuerpo
try {
console.log(restar(10, 3)); // TypeError: restar is not a function
} catch (e) {
console.log(e.message);
}
var restar = function(a, b) {
return a - b;
};
// ❌ Arrow function — mismo comportamiento que expresión
try {
console.log(multiplicar(2, 5));
} catch (e) {
console.log("multiplicar no está lista aún");
}
const multiplicar = (a, b) => a * b;// ✅ Declaración de función — hoisting completo
console.log(sumar(3, 4)); // 7
function sumar(a: number, b: number): number {
return a + b;
}
// ❌ TypeScript detecta este error en tiempo de compilación
// console.log(restar(10, 3)); // Error: Block-scoped variable used before declaration
const restar = (a: number, b: number): number => a - b;
// TypeScript protege contra muchos errores de hoisting
// gracias al análisis estático7
restar is not a function
multiplicar no está lista aúnlet y const — Temporal Dead Zone (TDZ)
let y const también hacen hoisting — pero no se inicializan. Acceder a ellas antes de su declaración lanza un ReferenceError. Ese período se llama Temporal Dead Zone (TDZ).
// ❌ TDZ — ReferenceError
try {
console.log(nombre); // ReferenceError: Cannot access before initialization
let nombre = "Carlos";
} catch (e) {
console.log(`Error: ${e.message}`);
}
// ✅ Uso correcto — declarar antes de usar
let contador = 0;
contador++;
console.log(contador); // 1
// ✅ const también tiene TDZ
const PI = 3.14159;
console.log(PI); // 3.14159
// Comparación clara:
{
// TDZ de "letVar" comienza aquí ↑
// console.log(letVar); // ❌ ReferenceError
let letVar = "seguro";
// TDZ de "letVar" termina aquí ↓
console.log(letVar); // "seguro"
}// TypeScript detecta el error TDZ en compilación
// console.log(nombre); // Error de compilación
// let nombre: string = "Carlos";
// ✅ Orden correcto
let nombre: string = "Carlos";
console.log(nombre); // "Carlos"
// const con tipo explícito
const PI: number = 3.14159;
console.log(`PI = ${PI}`); // "PI = 3.14159"
// En TypeScript, el compilador te avisa antes
// de que el runtime lance el errorError: Cannot access 'nombre' before initialization
1
3.14159
seguro| Tipo | Hoisting | Valor antes de declaración | Re-asignable |
|---|---|---|---|
| var | Sí — declaración sube | undefined (silencioso) | Sí |
| let | Sí — pero en TDZ | ReferenceError (explícito) | Sí |
| const | Sí — pero en TDZ | ReferenceError (explícito) | No |
| function declaration | Sí — completa | Función disponible | Sí |
El error de TDZ con let/const es más útil que el undefined silencioso de var. Te dice exactamente qué salió mal en lugar de dejar que el bug se propague silenciosamente.