Contexto de ejecución de JavaScript y pila de llamadas

Si usted es un desarrollador de JavaScript o desea convertirse en uno, esto significa que debe comprender los mecanismos internos para ejecutar el código JS. En particular, es absolutamente necesario comprender cuál es el contexto de ejecución y la pila de llamadas para dominar otros conceptos de JavaScript, como elevar variables, alcance y cierre. El material, cuya traducción publicamos hoy, está dedicado al contexto de ejecución y la pila de llamadas en JavaScript.



Contexto de ejecución


El contexto de ejecución es, en términos simplificados, un concepto que describe el entorno en el que se ejecuta el código JavaScript. El código siempre se ejecuta dentro de un contexto.

▍ Ejecutar tipos de contexto


JavaScript tiene tres tipos de contextos de ejecución:

  • Contexto de ejecución global. Este es el contexto básico de ejecución predeterminado. Si algún código no está dentro de ninguna función, entonces este código pertenece al contexto global. El contexto global se caracteriza por la presencia de un objeto global, que, en el caso del navegador, es el objeto de window , y el hecho de que this apunta a este objeto global. Un programa solo puede tener un contexto global.
  • Contexto de ejecución de funciones. Cada vez que se llama a una función, se crea un nuevo contexto para ella. Cada función tiene su propio contexto de ejecución. Un programa puede tener simultáneamente muchos contextos para ejecutar funciones. Al crear un nuevo contexto para la ejecución de una función, pasa por una cierta secuencia de pasos, que discutiremos a continuación.
  • El contexto de ejecución de la función eval . El código ejecutado dentro de la función eval también tiene su propio contexto de ejecución. Sin embargo, la función eval se usa muy raramente, por lo que aquí no hablaremos sobre este contexto de ejecución.

Pila de ejecución


La pila de ejecución, que también se llama pila de llamadas, es la pila LIFO que se utiliza para almacenar contextos de ejecución creados durante la ejecución del código.

Cuando el motor JS comienza a procesar el script, crea un contexto de ejecución global y lo coloca en la pila actual. Cuando se detecta un comando para llamar a una función, el motor crea un nuevo contexto de ejecución para esta función y lo coloca en la parte superior de la pila.

El motor realiza una función cuyo contexto de ejecución está en la parte superior de la pila. Cuando se completa la función, su contexto se elimina de la pila y el control se transfiere al contexto que se encuentra en el elemento anterior de la pila.

Exploraremos esta idea con el siguiente ejemplo:

 let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context'); 

Así es como cambiará la pila de llamadas cuando se ejecute este código.


Estado de la pila de llamadas

Cuando el código anterior se carga en el navegador, el motor de JavaScript crea un contexto de ejecución global y lo coloca en la pila de llamadas actual. Al realizar una llamada a la first() función first() , el motor crea un nuevo contexto para esta función y la coloca en la parte superior de la pila.

Cuando se llama a la second() función second() desde la first() función first() , se crea un nuevo contexto de ejecución para esta función y también se inserta en la pila. Después de que la second() función second() completa su trabajo, su contexto se elimina de la pila y el control se transfiere al contexto de ejecución ubicado en la pila debajo de ella, es decir, al contexto de la first() función first() .

Cuando sale la first() función first() , su contexto se extrae de la pila y el control se transfiere al contexto global. Después de ejecutar todo el código, el motor recupera el contexto de ejecución global de la pila actual.

Acerca de crear contextos y ejecutar código


Hasta ahora, hablamos sobre cómo el motor JS gestiona los contextos de ejecución. Ahora hablemos sobre cómo se crean los contextos de ejecución y qué les sucede después de que se crean. En particular, estamos hablando de la etapa de creación del contexto de ejecución y la etapa de ejecución del código.

▍ Etapa de creación del contexto de ejecución


Antes de ejecutar el código JavaScript, se crea el contexto de ejecución. En el proceso de su creación, se realizan tres acciones:

  1. Este valor se determina y this (este enlace) está vinculado.
  2. Se LexicalEnvironment componente LexicalEnvironment .
  3. Se crea el componente VariableEnvironment .

Conceptualmente, el contexto de ejecución se puede representar de la siguiente manera:

 ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 

Esta unión


En el contexto de ejecución global, this contiene una referencia al objeto global (como ya se mencionó, en el navegador es un objeto de window ).

En el contexto de la ejecución de la función, el valor de this depende de cómo se llamó a la función. Si se llama como método de un objeto, entonces el valor de this vinculado a este objeto. En otros casos, this vinculado a un objeto global o establecido en undefined (en modo estricto). Considere un ejemplo:

 let foo = { baz: function() { console.log(this); } } foo.baz();    // 'this'    'foo',    'baz'              //    'foo' let bar = foo.baz; bar();       // 'this'     window,                 //      

Entorno léxico


De acuerdo con la especificación ES6, Lexical Environment es un término que se utiliza para definir la relación entre los identificadores y las variables y funciones individuales en función de la estructura de la anidación léxica del código ECMAScript. El entorno léxico consta de un Registro de entorno y una referencia al entorno léxico externo, que puede ser null .

En pocas palabras, un entorno léxico es una estructura que almacena información sobre la correspondencia de identificadores y variables. Aquí, por "identificador" se entiende el nombre de una variable o función, y por "variable" es una referencia a un objeto específico (incluida una función) o un valor primitivo.

En el entorno léxico hay dos componentes:

  1. Registro de un entorno. Aquí es donde se almacenan las declaraciones de variables y funciones.
  2. Enlace al entorno externo. La presencia de dicho enlace indica que el entorno léxico tiene acceso al entorno léxico principal (ámbito).

Hay dos tipos de entornos léxicos:

  1. El entorno global (o el contexto de ejecución global) es un entorno léxico que no tiene un entorno externo. La referencia del entorno global al entorno externo es null . En el entorno global (en el registro del entorno), las entidades de lenguaje integradas (como Object , Array , etc.) están disponibles y están asociadas con el objeto global, también hay variables globales definidas por el usuario. El valor de this en este entorno apunta a un objeto global.
  2. El entorno de la función en la que, en el registro del entorno, se almacenan las variables declaradas por el usuario. La referencia al entorno externo puede indicar tanto un objeto global como una función externa a la función en cuestión.

Hay dos tipos de registros de entorno:

  1. Un registro de entorno declarativo que almacena variables, funciones y parámetros.
  2. Un registro de objeto de entorno que se utiliza para almacenar información sobre variables y funciones en un contexto global.

Como resultado, en un entorno global, un registro de entorno está representado por un registro de entorno de objeto, y en un entorno de función, por un registro de entorno declarativo.

Tenga en cuenta que en el entorno de la función, el registro declarativo del entorno también contiene el objeto de arguments , que almacena la correspondencia entre los índices y los valores de los argumentos pasados ​​a la función, e información sobre el número de tales argumentos.

El entorno léxico se puede representar como el siguiente pseudocódigo:

 GlobalExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //        }   outer: <null> } } FunctionExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //        }   outer: <        > } } 

Variables de entorno


Un entorno variable también es un entorno léxico cuyo registro de entorno almacena los enlaces creados utilizando los comandos VariableStatement en el contexto de ejecución actual.

Dado que el entorno de las variables también es un entorno léxico, posee todas las propiedades descritas anteriormente del entorno léxico.

En ES6, hay una diferencia entre los componentes LexicalEnvironment y VariableEnvironment . Consiste en el hecho de que el primero se usa para almacenar declaraciones de funciones y variables declaradas usando las palabras clave let y const , y el segundo se usa solo para almacenar enlaces de variables declarados usando la palabra clave var .

Considere ejemplos que ilustran lo que acabamos de discutir:

 let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30); 

Una representación esquemática del contexto de ejecución para este código se verá así:

 GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          a: < uninitialized >,     b: < uninitialized >,     multiply: < func >   }   outer: <null> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          c: undefined,   }   outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          Arguments: {0: 20, 1: 30, length: 2},   },   outer: <GlobalLexicalEnvironment> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          g: undefined   },   outer: <GlobalLexicalEnvironment> } } 

Como probablemente haya notado, las variables y constantes declaradas usando las palabras clave let y const no tienen valores asociados, y las variables declaradas usando la palabra clave var se establecen como undefined .

Esto es así porque durante la creación del contexto, el código busca declaraciones de variables y funciones, mientras que las declaraciones de funciones se almacenan completamente en el entorno. Los valores de las variables, cuando se usa var , se establecen como undefined , y cuando se usa let o const permanecen sin inicializar.

Es por eso que puede acceder a las variables declaradas con var antes de que se declaren (aunque no estarán undefined ), pero cuando intenta acceder a las variables o constantes declaradas con let y const ejecutadas antes de que se declaren, se produce un error .

Lo que acabamos de describir se llama "variables de elevación". Las declaraciones de variables "suben" a la parte superior de su alcance léxico antes de realizar operaciones de asignación de valores.

▍ Etapa de ejecución del código


Esta es quizás la parte más simple de este material. En esta etapa, los valores se asignan a las variables y se ejecuta el código.

Tenga en cuenta que si, durante la ejecución del código, el motor JS no puede encontrar el valor de la variable declarada utilizando la palabra clave let en el lugar de la declaración, asignará a esta variable el valor undefined .

Resumen


Acabamos de discutir los mecanismos internos para ejecutar código JavaScript. Aunque para ser un muy buen desarrollador de JS, no es necesario saber todo esto, si comprende algo de los conceptos anteriores, lo ayudará a lidiar mejor y más profundamente con otros mecanismos del lenguaje, como elevar variables, alcance, Cortocircuitos.

Estimados lectores! ¿Qué más crees que aparte del contexto de ejecución y la pila de llamadas son útiles para que los desarrolladores de JavaScript sepan?

Source: https://habr.com/ru/post/es422089/


All Articles