El único ejemplo que necesitas para entender Global Execution Context en Javascript

El concepto de Execution Context es algo normalmente reservado para solo los desarrolladores avanzados de Javascript. No es común que se considere un tema digno de enseñarse al principio.

Sin embargo entenderlo es la vía a hacerte la vida mas fácil cuando estes debuggeando o aprendiendo otros conceptos del lenguaje, como this o el Call Stack.

En esta primera parte veremos como se comporta el Execution Context principal de cualquier programa de JS.

¿Qué es?

Primero lo primero. ¿Qué es un Execution Context?

Un execution context se traduce literalmente a “contexto de ejecución”. Como el nombre lo dice, se refiere precisamente a la parte desde donde se esta ejecutando el código.

Hay 2 tipos de execution contexts:

  • Global Execution Context
  • Function Execution Context

El Global Execution Context (GEC) es el contexto que se crea a nivel global de tu código. Como te puedes imaginar es el contexto de ejecución principal. Todo lo que escribes corre en el GEC.

El Function Execution Context (FEC) es el contexto de ejecución que se crea dentro de una función. Cada función que mandes a llamar (ojo: que mandes a llamar, no que definas) creará un FEC durante la ejecución.

*Si ya dominas GEC puedes empezar a ver FEC aquí.

¿Cómo se crea?

Tu no eres el encargado de crear ningún Execution Context. Esto podrá parecer obvio pero no quiero que te me confundas.

El motor de Javascript es el encargado de realizar este proceso al momento de leer tu código. Y para lograrlo se hace a través de 2 fases:

  1. Fase De Creación – Se preparan los objeto, los espacios en memoria y las referencias
  2. Fase de Ejecución – Se asignan los valores de las variables

¿Cómo se conforma?

*Hay varios objetos que componen un Execution Context, cada uno para un propósito diferente. No te agobies con las definciones. Es importante que te familiarices con los términos pero más adelante veremos unos ejemplos.

Execution Context

Los primeros objetos que se encuentran dentro de un Execution Context son los Lexical Environments.

Un lexical environment (ambiente léxico) es una estructura que contiene las referencias a los identificadores distintas propiedades. Es decir referencias a variables y funciones declaradas.

Solo hay 2 tipos de Lexical Environment y todos los Execution Contexts los tienen (ya sea los utilizen o no):

  1. Lexical Environment – Es el ambiente primario y guarda toda las funciones y todas las variables declaradas con let y const.
  2. Variable Environment – Es el ambiente utilizado para variables declaradas con var.

Lexical Environment/Variable Environment

A su vez cada Lexical Environment contiene 2 objetos indispensables:

  1. Environment Record – Es donde se almacenan las referencias de las variables y funciones. Este se encuentra compuesto de otros Environment Records.
    Es importante que tengas en cuenta que solo las variables declaradas con var se alamacenan aquí durante esta fase. Variables decalradas con let y const se guardan en un lugar comúnmente referido por la comunidad como TDZ (Temporal Data Zone o Temporal Dead Zone).
  2. OuterReference – Es la referencia al Execution Context anterior (también llamado Execution Context padre).

*Muchas veces encontrarás que se menciona que el Lexical Environment contiene tambíen a la referencia this. Esto es técnicamente correcto, pero siendo más específicos esta referencia se encuentra dentro de un los Environment Records internos.

Environment Record

Por último cada Environment Record puede ser 1 de 3 tipos diferentes y cada uno contiene diferentes elementos:

Declarative Environment Record con 4 elementos internos: Classes, Modules, Functions, Variables.
Object Environment Record con 3 elementos internos: Variable Object, Argument Object, this Binding.
Global Environment Record con 2 elementos internos: Declarative Environment Record. Object Environment Record.

El Declarative Environment Record guarda referencias a conceptos que se explican por sí mismos. Esto es lo que permite que se manden a llamar funciones y variables directamente.

Los 3 elementos que componen al Object Environment Record se refieren a:

  1. Variable Object (VO) – Es el objeto que almacena las variables declaradas con var y funciones declaradas. Este objeto es el mismo que el objeto global, por lo que solo esta presente dentro de un Global Execution Context. Las variables declradas con let y const se mantienen fuera del scope de este objeto por lo que solo se pueden llamar gracias al Declarative Environment Record.
  2. Argument Object (AO) – Es el objeto especial arguments que contiene los parámetros que se pasaron a una función. Por esto mismo solo esta presente dentro de un Function Execution Context.
  3. this Binding– La keyword this referencia al objeto que creo el Execution Context.

Finalmente el Global Environment Record solo funciona como un objeto compuesto de los otros 2 Environment Records. A pesar de ser llamado global por lógica dentro de un FEC también puede existir un Environment Record que este compuesto de los otros 2.

El Ejemplo

Como ves son muchos conceptos a aprenderse. Pero es más fácil visualizarlos con un ejemplo. Observa el siguiente bloque de código:

let a = 10;
const b = 20;
var c = 30;

function hello() {
    console.log(hola)
}

Tanto las variables como la función se encuentran a nivel global y el código se ejecuta en un navegador web.

¿Como funcionarían los execution contexts con sus lexical environments?

Fase de Creación

Javascript crea un Global Execution Context y crea 2 lexical environments para el mismo. Cada Lexical Environment a su vez ya contiene sus propios objetos reservados para sus Environment Records, incluyendo la Outer Reference y el this binding.

Ambos Environment Records a su vez ya contienen 2 espacios para sus respectivos Declarative Environment Record y Object Environment Record.

1. El objeto inicial se vería de esta manera:

Global Execution Context, con 1 Lexical y Variable Environment . Ambos tienen su propio Globlal Environment Record y un Outer Reference con valor null.
Ambos Global Environment Record tienen un Declarative y un Object Environment Record. Ambos Object Environment Record tienen un VO y un this Binding con valor null.

2. Posteriormente JS define el VO, el objeto global, dentro del Object Environment Record de ambos Lexical Environments.

Como en este ejemplo estamos corriendo el código en un navegador web ese objeto es window.

Diagrama del Lexical Environment con el valor window asignado al VO de su Object Environment Record.
Diagrama del Variable Environment con el valor window asignado al VO de su Object Environment Record.

3. Al leer el código JS encuentra que hay 1 variable declarada con var. Esta variable la asigna al Variable Environment, dentro de su Declarative Environment Record

Diagrama del Variable Environment con el valor undefined asignado a la variable c, de su Declarative Environment Record.

Esta variable se encuentra con valor undefined porque en la fase de creación JS solo esta haciendo el proceso de hoisting. Es decir solo esta guardando referencias para las variables.


4. Posteriormente JS encuentra que hay 1 variable declarada con let, 1 con const y una función. Por lo que las asigna al Lexical Environment primario…¿no?

Diagrama del Lexical Environment con el valor <ref to function> asignado a la variable hello, de su Declarative Environment Record.

Pero…un momento. Claramente hace falta a y b. ¿Porqué no estan en el Declarative Environment Record?

Recuerda que con let y const no se guardan las variables en el Environment Record y en su lugar se mantienen en el TDZ. Esto es porque estas variables fueron introducidas posteriormente en la versión de ES6 de Javascript con la intención de comportarse diferente a var. Sin embargo se inicializan también con el valor de undefined.

*Si quieres tener un entendimiento mas claro de las diferencias entre var vs let/const puedes revisar este artículo.

Diagrama del Lexical Environment con el valor <ref to function> asignado a la variable hello, de su Declarative Environment Record y con una Temporal Dead Zone creada que contiene 2 variables: a y b, con valor de undefined.

5. JS procede a asignar el valor de la Outer Reference. En este caso estamos trabajando con el Global Execution Context y por lo tanto no hay un Execution Context anterior. Esto hace que el valor se quede sin asignar.

Por último JS asigna el valor de this al objeto global, que es el que se uso para crear el GEC.

Al final de la fase de creación nos quedamos entonces con este Lexical Environment y Variable Environment:

Diagrama del Lexical Environment con el valor <ref> asignado al this Binding de su Object Environment Record y con el valor null asignado a su OuterReference.
Diagrama del Variable  Environment con el valor <ref> asignado al this Binding de su Object Environment Record y con el valor null asignado a su OuterReference.

Fase de Ejecución

En la fase de ejecución, el engine de Javascript lee el código una vez mas y conforme va encontrando líneas donde se asignan valores va actualizando las referencias correspondientes en los lexical environments.

Recuerda la función:

let a = 10;
const b = 20;
var c = 30;

function hello() {
    console.log(hola)
}

Javascript ejecuta línea por línea:

Línea 1: Asigna 10 a la variable a.

Se saca la variable de TDZ y se coloca su valor dentro del Lexical Environment.

Diagrama del Lexical Environment con la variable a copiada en su Declarative Environment Record con el valor de 10. Temporal Dead Zone sin la propiedad a.

Línea 2: Asigna b a la variable con b.

Se saca la variable de TDZ y se coloca su valor dentro del Lexical Environment.

Diagrama del Lexical Environment con la variable b copiada en su Declarative Environment Record con el valor de 20. La Temporal Dead Zone se ha eliminado.

Línea 3: Se asigna el valor de 40 a la variable c dentro del Variable Environment.

Diagrama del Variable Environment con el valor 30 asignado a la variable c en su Declarative Environment Record.

Línea 4-6: Contiene la definción de la función. Pero en este punto JS ya había guardado la referencia en la fase anterior.


¡Listo! Ese fue el ciclo de creación y ejecución de tu Global Execution Context.

Son muchos conceptos, pero una vez que los comprendes el proceso es bastante sencillo.

Pero el tema de los execution contexts no termina aquí. La siguiente pregunta a responder es: ¿Qué hay de los Function Execution Context?