Cómo funciona Javascript
Javascript es un lenguaje muy extendido. De hecho, es difícil encontrar algún desarrollador que no haya tirado algunas líneas de código Javascript en algún momento, ya que son innumerables sus aplicaciones.
Sin embargo, no todos los desarrolladores entienden al detalle algunas características de Javascript que son un poco distintas en otros lenguajes de programación, por ello es habitual que surjan dudas, problemas o que se lleguen a crear aplicaciones poco fiables. Esperamos en este post ayudar a los lectores si es el caso.
Javascript es un lenguaje asíncrono no bloqueante
Javascript solo mantiene un único hilo de ejecución, por tanto, no es capaz de ejecutar dos líneas de código a la vez. Pero con esta limitación, ¿por qué Javascript consigue ser más rápido que otros y atender diversos procesos dentro del contexto de una página web? ¿Cómo consigue atender a gran cantidad de usuarios que consultan un servicio web desarrollado en NodeJS al mismo tiempo?
La respuesta la encontramos en su capacidad asíncrona, algo que han ido imitando otros lenguajes tradicionales. Asíncrono significa que es capaz de ejecutar procesos que tardan determinado tiempo, volviendo al punto de ejecución donde estaba cuando ese proceso acabe. No bloqueante, además, indica que durante ese tiempo de espera Javascript no hace que se pare el hilo de ejecución hasta que termine el proceso, sino que lo libera para que el lenguaje pueda encargarse de otras cosas.
Esta condición del lenguaje se comenta mucho, incluso en cursos de iniciación, pero veamos un ejemplo para asegurarnos que queda claro.
console.log('hola'); setTimeout(() => console.log('adiós')); console.log('nos vemos');
¿Cuál es el orden de aparición de los mensajes? Seguro que lo has adivinado. Sería: ‘hola’, ‘nos vemos’ y finalmente ‘adiós’. El método setTimeout es asíncrono, él espera un cierto número de milisegundos para ejecutarse, pero mientras tanto, ha dejado liberado el flujo de ejecución y procesado el último console.log antes.
Flujo de ejecución con doble pasada
Otra cosa importante de Javascript es que el flujo de ejecución implica dos pasadas.
- En la primera pasada se detectan todas las variables que han sido creadas en el código y se reserva para ellas un espacio en la memoria. Sin embargo, no se les asignan valores, sino que las variables quedarán en estado «undefined».
- En la segunda pasada se ejecutan todas las instrucciones de código y las variables van tomando los valores que se hayan asignado durante esa ejecución.
Sabiendo esto, ¿qué piensas que pasará en el siguiente código?
console.log(milenguaje); var milenguaje = 'Javascript'; console.log(milenguaje);
En muchos lenguajes de programación, este código daría un error porque se intenta acceder a una variable antes de su ejecución. Sin embargo, en Javascript no se produce el error, porque cuando se ejecuta el primer console.log la variable ya existe, debido a la primera pasada.
Por tanto, el código anterior imprimiría en la consola primero «undefined» y luego «Javascript».
Contexto de ejecución
En Javascript existe lo que denominamos contexto de ejecución o «execution context». Este contexto de ejecución contiene dos áreas de trabajo.
- Memoria: durante la primera pasada se van guardando todas las variables en la memoria, así como las funciones.
- Código: durante la segunda pasada se ejecuta el código teniendo presentes todas las variables que existen en la memoria del contexto de ejecución.
Por eso, a vista de este código:
var foo = 'foo'; function xyz() { console.log(puntos); var puntos = 10; } xyz();
En la primera pasada se meterán en memoria la variable «foo» con valor «undefined» y se almacenará también en la memoria la función xyz.
Luego, en una segunda pasada (la ejecución) será cuando se asigne el valor a la variable foo, que dejaría de valer «undefined», y se ejecuta la función xyz.
Pero es que, además, las funciones generan su propio contexto de ejecución particular. Por tanto, cuando se ejecuta la función también ocurrirá esta doble pasada, en la primera se generará la variable «puntos» y en la segunda se ejecutará su código. Por tanto ¿cuál será la salida por consola al ejecutar ese código? Lo habrás adivinado: «undefined».
Este comportamiento de creación de las variables y por tanto su existencia antes de ejecutarse el código se conoce comúnmente como «hoisting».
Declaración let y const
Para evitar despistes y otras situaciones similares es por lo que se creó la declaración «let». Ahora veamos este código.
console.log(empresa); let empresa = 'Arsys';
¿Qué crees que pasará? Si ejecutamos el código en modo estricto, nos informará que no podemos acceder a una variable antes de inicializarse.
Por lo que respecta al hoisting la diferencia con let y const es que las variables no se inicializan con undefined, aunque las variables sí que se tienen en cuenta en la primera pasada.
Acceso al contexto de ejecución superior
La función, al estar situada en un contexto de ejecución, es capaz de acceder a las variables de ese contexto de ejecución, es decir, al suyo propio y al contexto del padre.
Veamos este código:
var empresa = 'Arsys'; function mostrar() { console.log(empresa, actividad); var puntos = 10; } mostrar(); var actividad = 'Hosting';
¿Qué crees que saldrá en la consola? ¿Lo has adivinado esta vez? Será: «Arsys undefined». Por dos puntos:
- Por la regla de hoisting, la variable actividad vale undefined cuando se ejecuta la función.
- Por la función estar dentro del contexto global, es capaz de acceder a las variables que existen fuera.
Call stack
Al producirse llamadas a funciones se va realizando una pila de ejecución, en la que cada función crea su propio contexto de ejecución. Ese contexto tiene su propio ámbito, que incluye la misma función y todas las variables conocidas en contextos padre. A medida que las funciones se van finalizando, los contextos de ejecución creados para ellas se van eliminando y por tanto la pila de llamadas se va vaciando.
Cuando termina el contexto global de Javascript, también se elimina y se libera todo el espacio de la memoria de funciones y variables, como antes había ocurrido con el contexto de particular cualquier función ejecutada. Esto puede ocurrir cuando el usuario se va de una página o cuando termina la ejecución de un script NodeJS.
Conclusión
Esperamos que todas estas notas te hayan aportado algo de idea sobre cómo funciona Javascript y que puedas entender mejor el lenguaje de programación y estar atento a las posibles situaciones en las que no es del todo claro.