Antes de ejecutar cualquier línea de código, JavaScript hace una pasada por el scope y registra todas las declaraciones de variables y funciones. Las mueve — conceptualmente — al tope del scope. Eso es hoisting.
No es magia. Es una consecuencia de cómo el motor de JavaScript compila y luego ejecuta el código en dos fases separadas. En la primera fase registra; en la segunda ejecuta. El hoisting es un efecto de esa separación.
Funciones: hoisting completo
Las declaraciones de función son las que se hoistean más generosamente. Están completamente disponibles antes de la línea donde las escribiste.
saludar() // funciona, imprime "Hola"
function saludar() {
console.log('Hola')
}
Esto no es un error. JavaScript ya registró saludar y su implementación completa en la fase de compilación. Cuando llega a la llamada, la función existe.
Las arrow functions y funciones asignadas a variables no funcionan igual — heredan el comportamiento de la variable a la que están asignadas.
var: hoisting con trampa
Las variables declaradas con var se hoistean, pero solo la declaración — no la asignación. El motor sabe que la variable existe, pero su valor es undefined hasta que la ejecución llega a la línea de asignación.
console.log(nombre) // undefined — no da error, pero tampoco tiene valor
var nombre = 'Elias'
console.log(nombre) // "Elias"
Lo que JavaScript ejecuta internamente es algo así:
var nombre // hoisted — existe, vale undefined
console.log(nombre) // undefined
nombre = 'Elias'
console.log(nombre) // "Elias"
Este comportamiento es la raíz de bugs sutiles: la variable no tira un error, así que asumís que tiene un valor — pero tiene undefined. El error aparece más tarde, en otro lugar, y es difícil de rastrear.
let y const: las reglas cambian
Con let y const, el hoisting ocurre igual — el motor registra las declaraciones en la fase de compilación. Pero hay una diferencia crítica: no se pueden usar antes de la línea donde están declaradas.
console.log(nombre) // ReferenceError: Cannot access 'nombre' before initialization
let nombre = 'Elias'
El error es explícito. No hay undefined silencioso — el motor te dice exactamente qué pasó.
La Temporal Dead Zone
El período entre el inicio del scope y la línea donde let o const se inicializa se llama Temporal Dead Zone (TDZ). Durante ese período, la variable existe en el scope — el motor ya la registró — pero no se puede acceder a ella.
{
// --- TDZ de 'usuario' empieza acá ---
console.log(usuario) // ReferenceError
// --- TDZ de 'usuario' termina acá ---
let usuario = 'Elias'
console.log(usuario) // "Elias"
}
La TDZ no es un intervalo de tiempo — es un intervalo de código. Cuánto dure depende de dónde declaraste la variable dentro del scope, no de cuánto tarda en ejecutarse.
Por qué existe la TDZ
La TDZ no es un accidente — es una decisión de diseño deliberada.
Con var, el valor undefined antes de la asignación creaba una clase de bugs difíciles: código que corría sin errores pero con datos incorrectos. La TDZ invierte esa lógica: si accedés a una variable antes de inicializarla, el error es inmediato y explícito.
Un ReferenceError en el lugar exacto del problema es infinitamente más útil que un undefined que aparece tres funciones más adelante como resultado inesperado.
El patrón que evita todo esto
La regla práctica es simple: declarar antes de usar. Siempre. Y usar let y const en lugar de var.
// Mal — dependés de hoisting implícito
function calcular() {
console.log(total) // undefined, no error
var total = precio * cantidad
return total
}
// Bien — declaración antes de uso, error explícito si algo falla
function calcular() {
const total = precio * cantidad
console.log(total)
return total
}
const para valores que no cambian. let para valores que sí. var prácticamente nunca — no agrega nada que let no dé, y sí agrega comportamiento confuso.
El resumen en una línea por caso
- Declaraciones de función → hoisting completo, disponibles en todo el scope
- var → hoistea la declaración, no la asignación; valor
undefinedhasta la asignación - let / const → hoistea la declaración, pero la TDZ bloquea el acceso hasta la inicialización
Conocer la TDZ no cambia cómo escribís JavaScript el 99% del tiempo — si declarás antes de usar, nunca la tocás. Lo que cambia es que cuando sí aparece un ReferenceError de este tipo, sabés exactamente qué lo causó.