Entiende el Hoisting en JavaScript con Ejemplos Prácticos
Hoisting es un concepto fundamental del lenguaje JavaScript debido a que te ayuda a entender cómo tu código es ejecutado y por ello, es uno de los temas que se preguntan con frecuencia en las entrevistas (en serio) .
Aunque el concepto es muy sencillo, la mayoría de las veces se malinterpreta. En este artículo vas a aprender qué es hoisting con la ayuda de algunos ejemplos.
Lo que vas a aprender
Después de leer este artículo vas entender:
- El contexto de ejecución y sus fases.
- Cómo funciona el hoisting con variables (
var
) y funciones (function
) - Casos especiales de hoisting (
const
ylet
). - Qué es la Temporal Dead Zone.
Ejemplos
Considera este ejemplo:
var source = "escuelafrontend.com";console.log(source);function print() { console.log("desde print: " + source);}print();
Cuya salida es:
escuelafrontend.comdesde print: escuelafrontend.com
Es bastante sencillo, ¿verdad?
El código se ejecuta línea por línea.
Echemos un vistazo a lo que sucede:
- Se crea la variable
source
y se le asigna un valor aescuelafrontend.com
. - Se encuentra la línea de código
console.log
y el valor desource
se imprime en la consola:escuelafrontend.com
. - Del mismo modo, la siguiente línea es la definición de la función
print
. Seguido de la llamada real a la funciónprint()
. Esto resulta en las declaraciones dentro de la función que se ejecuta y la cadenadesde print: escuelafrontend.com
se imprime en la consola.
Pero, ¿es realmente tan sencilla la ejecución de código en JavaScript?
Echa un vistazo a esta variación del mismo ejemplo:
console.log(source);print();function print() { // definición de la función console.log("desde print: " + source);}var source = "escuelafrontend.com"; // declaración de la variable y asignación de su valorprint();
Aquí estamos llamando la variable source
en la consola y la función print
antes de que sean declaradas.
¿Crees que este código arrojará un error?
¿Cuál crees que es la salida esperada para este ejemplo?
Tómate un momento para pensar antes de ver la respuesta más abajo.
...
...
...
...
...
...
...
...
...
...
Resultado:
undefineddesde print: undefineddesde print: escuelafrontend.com
En la mayoría de los lenguajes de programación esto lanzaría una excepción.
Pero bueno, JavaScript lo permite. ¿Cómo? Debido al hoisting.
El concepto más popular de hoisting es:
Hoisting en JavaScript te permite acceder a funciones y variables antes de que hayan sido creadas.
Este concepto no es tan preciso, así que para entender cómo funciona el hoisting necesitamos entender el contexto de ejecución.
Contexto de Ejecución
El contexto de ejecución es el entorno que prepara el motor de JavaScript para ejecutar el código que escribimos.
En pocas palabras, es la información necesaria para ejecutar el código.
El contexto de ejecución se crea en dos fases:
Fase de Creación
- El código es escaneado/parseado para las variables y funciones.
- Se reserva espacio para las variables en memoria.
- Las definiciones de las funciones se cargan en la memoria.
Fase de Ejecución
- El código se ejecuta línea por línea con la información de la fase de creación.
- Se asignan valores a las variables.
- Se ejecutan las funciones.
Veamos estas fases para el ejemplo ilustrado anteriormente:
Encuentra la variable source
y la carga en la memoria. Como el código aún no se ha ejecutado su valor es undefined
.
La función printMe
también se carga en memoria.
Para comprobar si has entendido bien el concepto de hoisting y contexto de ejecución, veamos otro ejemplo:
source = 5;console.log(source);printFnExp();// sintaxis de la expresión de funciónvar printFnExp = function print() { console.log("desde la función: " + source);};var source;
Salida:
5Uncaught TypeError: printFnExp is not a function
¿Te causa sorpresa el error en la salida? No debería.
Para entenderlo mejor, echa un vistazo a los siguientes diagramas.
- Las variables
source
yprintFnExp
se cargan en memoria. - La función
print
se carga en la memoria.
Fíjate bien en printFnExp
: es una expresión de función. Esto indica que es una variable cuyo valor apunta a una función.
En términos simples, la función está asignada a una variable. Para llamar a la función que está asignada a una variable, tenemos que escribir “nombreDeLaFuncion” seguido de paréntesis. Por ejemplo: printFnExp()
- El valor 5 se asigna a
source
. - 5 se imprime en la consola.
- Se llama a
printFnExp
. Sin embargo, arroja un error -Uncaught TypeError: printFnExp no es una función
.
Esto sucede porque la variable fue “hoisteada”, pero su valor inicial sigue siendo undefined
. Por lo tanto, obtenemos un error por intentar llamar a una función sobre un valor indefinido.
La sentencia que asigna la referencia de la función print
a printFnExp
no se ha ejecutado.`
Para solucionar este error, vea los cambios en el código que aparecen a continuación:
source = 5;console.log(source);// sintaxis de la expresión de funciónvar printFnExp = function print() { console.log("desde la función: " + source);};printFnExp();var source;
Salida:
5desde la función: 5
Aquí se ha asignado a printFnExp
la referencia de la función print
. Por lo tanto, ahora es posible invocar la expresión de la función así - printFnExp();
Nota: Hay más en el contexto de ejecución que lo mencionado en este artículo. He cubierto sólo lo suficiente para que se entienda el concepto de hoisting.
Casos Excepcionales
El hoisting funciona de forma diferente si la declaración de la variable se realiza mediante let
o const
.
En el caso de var
el valor se inicializa a indefinido durante la fase de creación. Sin embargo, en el caso de let
y const
el valor sólo se inicializa durante la fase de ejecución.
Mira el ejemplo siguiente:
console.log(source);print();function print() { console.log("desde print: " + source);}let source = "escuelafrontend.com";
Salida:
Uncaught ReferenceError: source is not defined
Como el valor de source
no se inicializa durante la fase de creación, source
no tiene ninguna referencia al valor en memoria. Debido a esto, se lanza un error de referencia para la declaración console.log(source);
Este concepto también se conoce como Temporal Dead Zone (zona muerta temporal). Significa que no se puede acceder a una variable hasta que se declare.
Veamos el mismo ejemplo con estos conocimientos.
// Temporal Dead Zone/////////////////////////////////////////////// console.log(source); /// print(); /// /// function print() { /// console.log("desde print: " + source); /// } /// //////////////////////////////////////////////////let source = "escuelafrontend.com"; // Final de la Temporal Dead Zone
Arriba estamos representando la Temporal Dead Zone para la variable source
. Obtendremos un error de referencia si intentamos acceder a la fuente dentro de este bloque.
A continuación se muestra el uso correcto de let
:
let source = "escuelafrontend.com";console.log(source);function print() { console.log("desde print: " + source);}print();
Durante la fase de ejecución, si no se proporciona ningún valor junto con la declaración, el valor se considera undefined
.
Mira el ejemplo:
let source; // declaraciónconsole.log(source);source = "escuelafrontend.com"; // inicializaciónfunction print() { console.log("desde print: " + source);}print();
Salida:
undefinedfrom print: escuelafrontend.com
Ejemplo con const
:
const source;console.log(source)
Salida:
Uncaught SyntaxError: Missing initializer in const declaration
Una const
indica un valor constante. Este valor no puede ser cambiado durante la ejecución del código. Por lo tanto, tiene sentido que requiera un valor inicializador en el momento de la declaración.
Uso correcto de const
:
const source = "escuelafrontend.com";console.log(source);
Salida:
escuelafrontend.com
Recapitulación y Conclusión
En resumen, el mejor concepto de hoisting sería
Hoisting es cuando las funciones y las variables se almacenan en memoria para un contexto de ejecución antes de ejecutar nuestro código.
Las funciones se almacenan con una referencia a las funciones completas, las variables declaradas con var
con el valor de undefined
, y las variables declaradas con let
y const
se almacenan sin inicializar.
Las mejores prácticas sugieren declarar las variables al principio del bloque de código. También es preferible utilizar let
y const
para la declaración de variables. Esto mejora la legibilidad del código.
Aunque el uso de la declaración de variables mediante var
se ha convertido en algo obsoleto, es importante conocer este concepto, ya que es posible que te lo encuentres en algunas de las bases de código existentes o incluso en una entrevista donde tengas que refactorizar dicho código con las últimas características de JavaScript.
El conocimiento del hoisting te ayudará a evitar cualquier error y confusión relacionados con la declaración de variables y su uso.
Otros Recursos
- JavaScript: The Hard Parts, v2
- Hoisting - Wes Bos
- 🔥🕺🏼 JavaScript Visualized: Hoisting
- Hoisting - MDN, para tenerla en cuenta aunque no es muy clara la explicación.