JavaScript bajo el capó: bucle de eventos

Dec 01 2022
Dominemos JavaScript explorando su funcionamiento desde cero. ¿Alguna vez se ha enfrentado a errores indefinidos o ha tenido problemas para identificar el alcance de una variable? Realmente lleva mucho tiempo depurar cualquier error sin saber cómo funciona el código. En este blog, demostraré cómo funcionan realmente los conceptos avanzados como el bucle de eventos con respecto al contexto de ejecución, la pila de llamadas y la cola de devolución de llamadas.

Dominemos JavaScript explorando su funcionamiento desde cero

Foto de Marc St en Unsplash

¿Alguna vez se ha enfrentado a errores indefinidos o ha tenido problemas para identificar el alcance de una variable?

Realmente lleva mucho tiempo depurar cualquier error sin saber cómo funciona el código.

En este blog, demostraré cómo funcionan realmente los conceptos avanzados event loopcon respecto a execution context, call stacky .callback queue

Un descargo de responsabilidad: los conceptos son increíblemente intensivos en conocimiento y están interconectados, ¡así que ni siquiera parpadees!

Motor JavaScript

El motor V8 de Google es una ilustración bien conocida de un motor JavaScript. Por ejemplo, Chrome y Node.js emplean el motor V8. Básicamente, el motor V8 consta de dos partes:

  1. Pila de llamadas: todo el código se ejecuta dentro de ella. Funciona como la estructura de datos Stack, es decir, siguiendo el concepto LIFO (Last In First Out).
  2. Montón de memoria: donde se produce la asignación de memoria. A diferencia de la estructura de datos del montón, es solo una memoria.

El contexto de ejecución global o GEC es el contexto de ejecución predeterminado creado por el motor de JavaScript cada vez que se recibe un archivo de script.

Todo el código JavaScript que no está dentro de una función se ejecuta en formato GEC. Los siguientes pasos se realizan en GEC:

  • Construya un espacio de memoria para almacenar todas las variables y funciones a escala global
  • Generar un objeto global.
  • Generar la palabra clavethis

Dependiendo de dónde se ejecutará su código, determinará dónde thisse encuentra. Por ejemplo, en Node.js apunta a un objeto global distinto, mientras que en el navegador apunta al windowobjeto.

Consola del navegador

Pila de llamadas (o pila de ejecución de funciones)

JavaScript es single-threaded, como ya has escuchado. Pero, ¿qué significa realmente?

Significa que un motor de JavaScript contiene solo uno call stacko function execution stack.

  • Como sabemos, cada vez que el compilador explora su código por primera vez, el compilador le pide al motor JS que cree un Global Execution Contexto GECy que lo coloque en el archivo Call Stack.
  • Todo su código se ejecuta uno por uno en el Global Execution Contexty asigna memoria para la definición de funciones o la declaración de variables y lo almacena allí.
  • Pero cuando se encuentra cualquier llamada de función, se crea un Functional Execution Contexto FECpara ejecutar el código de la función y luego se agrega a la parte superior del archivo call stack.
  • El intérprete elimina una función de call stackcada vez que la función termina. Una función termina, cuando alcanza el final de su alcance o una declaración de retorno.
  • Finalmente, la ejecución de todo su código GECse elimina del archivo Call Stack.

No te preocupes, vamos a demostrarlo con un ejemplo.

function f1{
  console.log('f1');
}

f1();
console.log('end');

Paso 2: — En nuestro ejemplo, se ejecutará la primera línea, que es f1. Se asignará una memoria para f1su definición y se almacenará.

Paso 3: — En la segunda línea, se llama a una función. Para la invocación de esta función, se creará un Function Execution Context o y se almacenará encima del .FECCall Stack

Paso 4: — Ahora, todo f1()se ejecutará línea por línea y, una vez finalizada la ejecución, se eliminará del archivo Call Stack.

Paso 5: — Luego, console.log('end')se ejecutará la última línea y se imprimirá 'fin' en la consola. Finalmente, al ejecutar todo su código, Global Execution Contextse eliminará del archivo Call Stack.

¿Cómo gestiona JS las tareas asincrónicas?

JavaScript, como todos somos conocidos, es un lenguaje síncrono de un solo subproceso (una tarea a la vez), y el único subproceso que se call stackejecuta de inmediato, lo que sea que esté dentro de él.

Pero, ¿y si necesitamos ejecutar algo después de 5 segundos? ¿Podemos poner eso dentro del call stack?

No, no podemos. Porque call stackno tiene ningún temporizador. Pero, ¿cómo podemos hacer eso?

Aquí es donde entra en juego el tiempo de ejecución de JavaScript .

Entorno de tiempo de ejecución de JavaScript

Puede ser desconsolado si te lo digo ahora : no es parte de setTimeout()JavaScript, aunque los eventos DOM son partes de las API web que brindan acceso al motor JavaScript para usar todas sus propiedades en Global Execution Context (GEC) a través de el objeto mundial . Estas API web se denominan asíncronas .console.log()window

¿Qué es la cola de devolución de llamada?

Una cola de tareas denominada "cola de devolución de llamadas" o "cola de tareas" es aquella que se ejecuta después de que se hayan completado las tareas actuales de la pila de llamadas. Las tareas que están registradas en la API web pasan de la API web a la cola de devolución de llamada.

La cola de devolución de llamada funciona como una estructura de datos de cola, lo que significa que las tareas se manejan en orden FIFO (primero en entrar, primero en salir), a diferencia de una pila de llamadas, lo que significa que las tareas se manejan en el orden en que se agregaron a la cola.

Cola de devolución de llamada

¿Qué es el bucle de eventos?

Un bucle de eventos de JavaScript agrega una tarea de la cola de devolución de llamada a la pila de llamadas en orden FIFO tan pronto como la pila de llamadas esté vacía.

El bucle de eventos se bloquea si la pila de llamadas está ejecutando algún código y no agregará llamadas adicionales desde la cola hasta que la pila esté vacía una vez más. Esto se debe a que el código JavaScript se ejecuta de manera continua.

Entendamos los conceptos anteriores con un ejemplo.

  • Al principio, Global Execution Contextse crea para nuestro código dentro de nuestro call stacky GECejecuta nuestro código línea por línea.
  • GECejecuta la primera línea e imprime 'Inicio' en nuestra consola.
  • Al ejecutar la segunda línea, setTimeout()se llamará a la API web y luego setTimeout()dará acceso a la función de temporizador. Entonces podrá establecer 5000msun tiempo de retraso.
  • Cuando pase la callBack()función a través de setTimeout(), callBack()se registrará como una devolución de llamada a través de la API web web.
  • Y luego GECejecuta la primera línea e imprime 'Fin' en la consola.
  • Cuando se ejecute todo el código, GECse eliminará de nuestro archivo call stack.
  • Después 5000 millisecond, la callBack()función que está registrada en web API, se mueve dentro de call Back Queue.
  • Event looppone la callBack()función en el call Stackcuando se hace todo es trabajo. Y finalmente, la callBack()función se ejecuta e imprime 'Call Back' en la consola.

function f1() {
    console.log('f1');
}

function f2() {
    console.log('f2');
}

function main() {
    console.log('main');
    
    setTimeout(f1, 0);
    
    f2();
}

main();

Si está pensando que "f1" se imprimirá antes que "f2", entonces está equivocado. Será -

main
f2
f1

El mecanismo discutido de JavaScript es adecuado para cualquier función de devolución de llamada o solicitud de API.

Observemos paso a paso cómo funciona el segundo ejemplo dentro del entorno de ejecución.

  1. Al principio GECse creará dentro del call stacky luego se ejecutará el código línea por línea en GEC. Almacena todas las definiciones de funciones en el montón de memoria .
  2. Cuando main()se llama a, Function Execution Context (FEC)se crea a y luego entra en la pila de llamadas. Después de eso, todo el código de la main()función se ejecutará línea por línea.
  3. Tiene un registro de consola para imprimir la palabra principal. Entonces console.log('main')se ejecuta y sale de la pila.
  4. La setTimeout()API del navegador tiene lugar. La función de devolución de llamada lo coloca en la cola de devolución de llamada. Pero en la pila, la ejecución se produce como de costumbre, por lo que f2()entra en la pila. El registro de la consola de f2()ejecuciones. Ambos salen de la pila.
  5. Y luego el main()también sale de la pila.
  6. El bucle de eventos reconoce que la pila de llamadas está vacía y que hay una función de devolución de llamada en la cola. Por lo tanto, la función de devolución de llamada f1()se pondrá en la pila por el event loop. Comienza la ejecución. El registro de la consola se ejecuta y f1()también sale de la pila. Y finalmente, no hay nada más en la pila y en la cola para ejecutar más.

Es mi práctica y nota. Si lo encontró útil, muestre su apoyo haciendo clic en el ícono de aplausos a continuación. Puedes seguirme en medio .