Entendiendo el motor de JavaScript

September 11, 2021

JavaScript es uno de los lenguajes más populares y el favorito para la Web. Los Devs no tenemos que lidiar con compiladores para poder correr nuestro código JavaScript. El navegador compila en tiempo de ejecución, pasando de código a Bytecode. En este post me enfocaré en V8 Engine, desarrollado por Google y basado en Chromium, este es el mejor motor que existe y es utilizado por Browsers como Chrome, Edge, Brave, entre otros.


El V8 Engine

Te comente que el Engine transforma nuestro código JavaScript a Bytecode para que el Browser lo pueda interpretar.


Entorno Global

Una vez que llega un archivo de JavaScript al navegador y es ejecutado, el engine generará un entorno global que hace tres cosas muy importantes:


  • Objeto global llamado window. Si vamos a la consola podemos ver que window nos devuelve la API con todos los métodos que tenemos disponibles.

    gist of window on console

    Por ejemplo podríamos ejecutar window.console.log("Hello Chrome V8); y exactamente exactamente igual.

    window.console.log on console

  • Generará un contexto llamado this. En un contexto global this es igual a window.

this === window;
// true
  • Ambiente de ejecución.

Una vez generado este contexto global comienza el contexto de ejecución (Execution context) donde corre el código de JavaScript utilizando un Stack de tareas, a continuación el motor ejecutara los siguientes procesos:


  • Parser
  • AST
  • Interprete
  • Profiler y compiler

Tour por el Abstract syntaxt tree


Parser

Se genera un parseo del documento completo mediante palabras claves como function, var, etc. Existen dos tipos de parsers, el parser y el pre-parser. Con la finalidad de reducir el tiempo de carga.


El parser se encarga del código que se debe correr en el momento y el pre-parser aquel que se utilizará después. Con ejecutar después me refiero a tareas que se ejecutarán, por ejemplo, luego de una acción del usuario (click en un botón).


AST

El Abstract syntaxt tree (AST) se crea a partir de los nodos que genera el parser y es una estructura de árbol que representa tu código sintácticamente. Podemos utilizar la página AST Explorer y ver como este árbol es generado. Tur por el Abstract syntaxt tree


También podríamos generar JSX y ver cómo responde el árbol.


Una vez que el Bytecode es generado el AST es eliminado para liberar memoria.

Profiler y compiler

El profiler monitorea el código para optimizarlo. El compiler optimiza ese código y genera machine code (lenguaje binario). En esta etapa, y por la intención de optimizar el código, también genera errores como el Hoisting.


Analiza el siguiente código:

console.log(name); // undefined

var name = "Gaspar";

console.log(name); // "Gaspar"

En el primer console.log name es undefined, esto porque está haciendo referencia a una variable que no ha sido declarada. El motor de JavaScript sabrá que una variable name va a existir por lo que la declarara antes y le asignará undefined. Es como hacer lo siguiente:

var name = undefined;

console.log(name); // undefined

name = "Gaspar";

console.log(name); // "Gaspar"

Algo parecido ocurre con function, dónde podemos llamar una funcion antes de ser declarada y no necesariamente después:

hello("Gaspar"); // "Hello Gaspar"

function hello(name) {
  console.log(`Hello ${name}`);
}

hello("Pep"); // "Hello Pep"

A esto se le denomina Hoisting.


Take it easy. El código permanecerá igual, es solo una interpretación del engine de JavaScript que intenta optimizar nuestro código. Pero esto puede causar errores y resultados no esperados ya que el motor declara las variables y funciones en un scope superior, Global o de función.


Esto lo podremos contrarrestar si en lugar de utilizar var utilizamos let o const. Mira el siguiente código:

console.log(name); // ReferenceError: Cannot access 'name' before initialization

let name = "Gaspar";

Como puedes ver si utilizamos let o const para definir nuestras variables evitaremos el Hoisting y nuestras variables no se inicializará como undefined.

Podemos evitar el Hoisting en las funciones si utilizamos las arrow functions:

hello("Gaspar"); // ReferenceError: hello is not defined

const hello = (name) => {
  console.log(`Hello ${name}`);
};

Conclusiones

Ahora ya sabes el camino que tiene que seguir nuestro archivo JavaScript para poder ser interpretado y ejecutado dentro del navegador, también ya sabes que es el Hoisting y cómo evitarlo. Aún hay muchas cosas dentro del funcionamiento de Chrome V8, esto solo fue una breve introducción pero eres libre de revisar la documentación de V8 y también el GitHub de V8, es open source.