JavaScript
Hoisting
Explica cómo JavaScript prepara declaraciones antes de ejecutar un scope y compara el estado inicial de var, let, const, funciones, clases e imports.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
- Fundamentos de programación
JavaScript
Explica cómo JavaScript prepara declaraciones antes de ejecutar un scope y compara el estado inicial de var, let, const, funciones, clases e imports.
El hoisting describe que JavaScript registra las declaraciones y prepara sus bindings antes de ejecutar las sentencias de un scope.
No significa que el motor mueva físicamente el código hacia arriba.
console.log(value);
var value = 10;El resultado es:
undefinedComo modelo mental, un scope atraviesa dos momentos:
En este código:
var price = 100;
console.log(price);Durante la preparación se crea el binding price. Durante la ejecución se asigna 100 y después se imprime.
Este modelo es una simplificación útil. Los detalles internos se profundizan en Contextos de ejecución.
console.log(user); // undefined
var user = "Nicolás";
console.log(user); // "Nicolás"Una forma conceptual de visualizarlo:
var user;
console.log(user);
user = "Nicolás";
console.log(user);Solo la declaración está disponible antes; la asignación permanece en su posición original.
Una declaración de función queda inicializada con la función antes de ejecutar el código del scope.
greet(); // "Hello"
function greet() {
console.log("Hello");
}Por eso puede llamarse antes de la línea donde aparece.
Esto no significa que siempre sea la mejor organización. Colocar primero la intención principal y después las funciones auxiliares puede ser válido, pero debe mantenerse legible.
Una expresión de función sigue las reglas de la variable que la contiene.
greet(); // TypeError: greet is not a function
var greet = function () {
console.log("Hello");
};Antes de la asignación, greet vale undefined. Intentar ejecutarlo produce un TypeError.
greet(); // ReferenceError
const greet = function () {
console.log("Hello");
};El binding existe, pero permanece dentro de la Temporal Dead Zone.
Las arrow functions también son expresiones y dependen de la declaración que las contiene.
calculateTotal(); // ReferenceError
const calculateTotal = () => {
return 100;
};No existe un hoisting especial para las arrow functions.
A veces se dice que let y const “no tienen hoisting”. Esa frase es imprecisa.
console.log(value);
const value = 10;El binding local value ya fue creado para ese scope, pero todavía no está inicializado. Por eso JavaScript no busca un binding exterior ni devuelve undefined: lanza ReferenceError.
Ese periodo se llama Temporal Dead Zone.
Las clases se comportan de forma parecida a let y const.
const user = new User(); // ReferenceError
class User {}La declaración está registrada, pero no puede utilizarse antes de inicializarse.
Los imports se enlazan antes de ejecutar el cuerpo del módulo.
import { formatPrice } from "./formatters.js";Sus bindings son estáticos y de solo lectura desde el módulo consumidor. El sistema de módulos resuelve esas dependencias antes de ejecutar las sentencias del archivo.
No conviene pensar en los imports como variables que se asignan línea por línea.
Cada función prepara sus propias declaraciones al ser llamada.
function example() {
console.log(value); // undefined
var value = 10;
}
example();El value local oculta cualquier binding exterior desde el inicio del scope de función.
const value = 50;
function example() {
console.log(value); // undefined
var value = 10;
}El console.log no imprime 50, porque el binding local de var ya existe.
Las declaraciones let, const y class se preparan para su bloque completo.
{
console.log(status); // ReferenceError
const status = "ready";
}Las declaraciones de función dentro de bloques tienen reglas bien definidas en código estricto y módulos, pero históricamente tuvieron diferencias entre entornos.
Para evitar comportamiento confuso, no dependas de una función de bloque fuera de ese bloque.
if (isReady) {
function start() {
// ...
}
start();
}Algunos errores aparecen durante la preparación del scope.
const user = "Nicolás";
const user = "Nico";Produce un SyntaxError antes de ejecutar cualquier sentencia del archivo o bloque correspondiente.
Lo mismo ocurre con combinaciones incompatibles:
let value = 10;
var value = 20;Este código funciona:
runApplication();
function runApplication() {
loadConfiguration();
startServer();
}Pero este puede ser difícil de seguir:
processOrder();
function calculateDiscount() {
// ...
}
function validateStock() {
// ...
}
function processOrder() {
// ...
}El hoisting permite diferentes estilos de organización, pero la estructura debe comunicar la relación entre las funciones.
| Declaración | Estado antes de su línea | Resultado al acceder |
|---|---|---|
| `var value` | Inicializada con `undefined` | `undefined` |
| `let value` | Sin inicializar | `ReferenceError` |
| `const value` | Sin inicializar | `ReferenceError` |
| `class User` | Sin inicializar | `ReferenceError` |
| `function greet()` | Inicializada con la función | Puede llamarse |
| `var greet = function` | Variable en `undefined` | Acceso válido; llamada produce `TypeError` |
| `const greet = () => {}` | Sin inicializar | `ReferenceError` |
var también se adelanta.let, const y class no participan en hoisting.undefined de var con una variable inexistente.var se inicializa con undefined.let, const y class existen sin inicializar hasta su declaración.Predice el resultado:
console.log(first);
console.log(second);
var first = 10;
const second = 20;El primer console.log imprime undefined.
El segundo lanza un ReferenceError porque second está dentro de la Temporal Dead Zone. La ejecución se detiene en ese punto.
Temporal Dead Zone explica por qué un binding puede existir en el scope y aun así no estar disponible.