Qué es el hoisting y por qué no debería importarte

Enzo Díaz
5 min readSep 14, 2021

--

Seguramente habrás leído que el hoisting es cuando Javascript mueve tus declaraciones a la parte superior y bla bla.
No voy a confundirte, Javascript no mueve tus variables a ninguna parte sin tu permiso, se quedan ahí donde las dejaste.

Pero entonces, ¿qué es el hoisting?
El hoisting es el término usado para explicar un fenómeno natural que ocurre en Javascript con los espacios en memoria.

Para entenderlo, te resumo lo que ocurre internamente antes de correr tu código.

Existen dos “fases" en Javascript, donde primero se declaran tus variables y luego se ejecuta el código.
En la primera fase, se reservan en memoria todas las variables que tengas declaradas y se inicializarán con undefined.

💡undefined representa la ausencia de un valor.

A continuación, Javascript comenzará a correr línea por linea de arriba hacia abajo, y cuando llegue su momento, se asignará el valor correspondiente a la variable, tal y como lo decidiste.

💡Declarar una variable es la acción de reservar un espacio en memoria.

Veamos el ciclo de vida de una variable.

...
var name = "enzo"; // declarar e inicializar
console.log(name); // utilizar
} // y la variable se muere

¿Qué pasa si usamos una variable antes de declararla e inciarlizala?

... 
console.log(name); // Output: undefined
var name = “enzo";
}

A lo mejor esperabas un error por intentar acceder a un espacio en memoria que no existe, pero no. Recordemos que internamente, Javascript ya tiene un asiento reservado para lo que sea que vaya a estar ahí.

En forma de código, podríamos representar al fenómeno del hosting de la siguiente manera:

var name; // técnicamente vale undefined
console.log(name); // imprime undefined
name = “enzo"; // se asigna un primer valor

Como podemos ver, la variable name parece haber “subido”.

💡Hoist en inglés significa “elevar”

Pero en realidad sigue en el lugar; solo el espacio en memoria ha sido declarado antes de ser inicializado.

Este mismo comportamiento ocurre con otras declaraciones hoistables, como las funciones.

hi(); // Output: "hello"function hi(){
console.log("hello");
}

💡Existen otras declaraciones hoistables; const, let, var, function o class.

¿Por qué a los programadores les importa?

Acá es donde viene lo divertido.

Los programadores en Javascript consideramos que es buena práctica usar const y let para la declaración de variables.
Si preguntamos por qué, todos van a responder que “var es global”.
Pero no te atrevas a preguntar qué es “global”, la conversación no irá a ninguna parte… hasta ahora.

Para entender a ciencia cierta el hoisting, debemos entender que en Javascript existen cuatro contextos o alcances, ordenados jerárquicamente:

  • Global (toda la aplicación)
  • Module (un módulo de ES6)
  • Function (Sí, una función)
  • Block (Un bloque de código)

Un claro ejemplo de un espacio en memoria de alcance global es la función setInterval, o el objeto process en NodeJS.

💡Declara una variable con var, e intenta acceder a ella desde otra función o módulo y observa su comportamiento.

Notaremos que nuestras variables declaradas con var, solo son accesibles desde la propia función, porque definitivamente var no es global.
var es una declaración de alcance de función, y Javascript la “subirá” hasta la parte superior de la función en la que fue declarada.

Por otro lado tenemos las “nuevas” maneras de declarar variables con let y const.
Estas también se ven afectadas por el hositing, pero en su lugar, su alcance será de bloque; Javascript “subirá” estas declaraciones hasta el inicio de un bloque de código.

Curiosamente, Javascript no inicializa los espacios en memoria declarados con let y const.
Esto hará que utilizar una variable let o const antes de su inicialización nos arroje una excepción.

Esta última curiosidad nos provee una protección a prueba de despistes, por lo que la convierte en una alternativa más segura.

Con esto en mente, los programadores postmodernos nos la damos de listos y tomamos decisiones casi injustificadas. La más popular son las arrow functions.

Dedicaré un post entero solo a las funciones en otra ocasión. Hoy, sólo debemos entender que muchos programadores expresan funciones flechas para protegerlas del hositing:

hi(); // can't access lexical declaration 'hi' before initializationconst hi = () => {
console.log("hello");
}

Otro de los motivos por el cual usar let o const es evitar sobreescribir variables en el ámbito.
Veamos el comportamiento del siguiente código:

var name = "enzo";function greet(){
console.log(`Hi ${name}`);
var name = "pepe";
}
greet(); // ???

Instintivamente diremos :

eso sin duda imprime "Hi enzo".

Pero técnicamente sabemos la variable name dentro de la función, será “elevada” e inicializada con undefined, por lo que el resultado final será:
"Hi undefined".

Pero no te asustes, Javascript no ha sobreescrito la variable name en el scope superior. name seguirá valiendo "enzo" si es accedida desde otro scope donde no se haya declarado una variable del mismo nombre.
La variable name = "pepe" de la función, se morirá cuando la ejecución de la función acabe, por lo que no tendremos que preocuparnos por ella nunca más.

¿Por qué no debería importarme el hositing?

Bueno, no te voy a mentir.

Comprender el hoisting es clave para todo desarrollador Javascript, pero comprenderlo no nos da derecho a abusar de este comportamiento técnico.

Los lenguajes de alto nivel nacen para abstraernos de la complejidad interna, nosotros no deberíamos preocuparnos por ello.

Explotar las características de bajo nivel nos conducen a tomar malas decisiones de diseño.

Por ejemplo, ¿bajo qué contexto llamarías a una función que aún no ha sido declarada?
Hacer esto solo expresa una mala organización del código y dificulta su lectura natural (de arriba hacia abajo).

Evitar la redeclaración de variables podría ser un motivo noble, pero ¿por qué declararías dos veces la misma variable?
Los espacios en memoria deben ser siempre constantes.
La mutabilidad da lugar a efectos colaterales; el código nunca debería tener acceso de escritura a un espacio en memoria de un ámbito superior.
Esto solo denota una mala jerarquía y una mala planificación de las responsabilidades.

Así y más, abusar de los tecnicismos internos destapa una serie de codesmells que no queremos en nuestra aplicación.

Como ejercicio y desafío, desarrolla una aplicación utilizando var bajo el paradigma funcional.

Todos los errores que puedas llegar a tener, van a tener solución con buenas técnicas de composición y separación de responsabilidades.

Bajo el paradigma orientado a objetos, el comportamiento natural de la memoria puede ser fácilmente evitado utilizando una variable de “alcance de clase” (alcance de función) gracias this.
this es otra joyita de la que podemos hablar en este tema del hoisting y el scope, pero para no apagar el cerebro, dejaremos this para otra ocasión.

Desde ahora, en vez de pensar en let vs var, pensemos en scope, pensemos en jerarquías y pensemos en organización, en separación de responsabilidades, modularidad y en cómo un tema tan trivial puede tener un post tan denso como estos.

Quisiera agradecerte por llegar al final, y disculparme porque ahora voy a insistir en que utilices let y const.

Pues resulta que cuando trabajamos con equipos, las reglas suelen ser bastante caoticas, por lo que corremos el riesgo de que algún despistado repita el uso de nombres de variables en ámbitos muy cercanos.
Además, let vs const nos ofrece una claridad de lectura de código muy explicita; nos dice a gritos qué espacio en memoria es propenso a un cambio, y cual es totalmente seguro de utilizar.

No más vars “globales”.

--

--

Enzo Díaz

Programador y desarrollador. Convirtiéndome en arquitecto de software.