📜 ⬆️ ⬇️

Execution Context and Call Stack in JavaScript

If you are a JavaScript developer or want to become one, this means that you need to understand the internal mechanisms for running JS code. In particular, understanding what the execution context and call stack are is absolutely essential for mastering other JavaScript concepts, such as elevating variables, scopes, and closures. The material, the translation of which we are publishing today, is devoted to the execution context and call stack in JavaScript.



Execution context


The execution context is, to put it simply, a concept describing the environment in which the execution of JavaScript code is performed. The code is always executed inside some context.

Types of execution contexts


There are three types of execution contexts in JavaScript:
')

Execution stack


The execution stack, also called the call stack, is a LIFO stack that is used to store execution contexts created during code execution.

When the JS engine starts processing the script, the engine creates a global execution context and puts it on the current stack. When a function call command is detected, the engine creates a new execution context for this function and places it at the top of the stack.

The engine performs a function whose execution context is at the top of the stack. When a function completes, its context is removed from the stack and control is transferred to the context that is in the previous stack element.

We explore this idea with the following example:

 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'); 

This is how the call stack will change when this code is executed.


Call Stack Status

When the above code is loaded into a browser, the JavaScript engine creates a global execution context and places it in the current call stack. When the first() function is called, the engine creates a new context for this function and places it at the top of the stack.

When the second() function is called from the first() function, a new execution context is created for this function and also pushed onto the stack. After the second() function terminates, its context is removed from the stack and control is transferred to the execution context located on the stack below it, that is, to the context of the first() function.

When the first() function completes, its context is removed from the stack and control is transferred to the global context. After all the code is executed, the engine retrieves the global execution context from the current stack.

About context creation and code execution


So far, we have been talking about how the JS engine manages execution contexts. Now let's talk about how execution contexts are created and what happens to them after creation. In particular, we are talking about the stage of creating the execution context and the stage of code execution.

â–ŤStage of creating execution context


Before executing JavaScript code, an execution context is created. In the process of its creation, three actions are performed:

  1. The value of this determined and the binding of this (this binding) is performed.
  2. A LexicalEnvironment component (lexical environment) is created.
  3. The VariableEnvironment component is created.

Conceptually, the execution context can be represented as follows:

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

Binding this


In the global execution context, this contains a reference to the global object (as already mentioned, in the browser this is the window object).

In the context of executing a function, the value of this depends on how the function was called. If it is called as an object method, then the value of this bound to this object. In other cases, this bound to a global object or set to undefined (in strict mode). Consider an example:

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

Lexical environment


According to the ES6 specification , the lexical environment (Lexical Environment) is a term used to define the relationship between identifiers and individual variables and functions based on the lexical nesting structure of the ECMAScript code. A lexical environment consists of an environment record (Environment Record) and a reference to an external lexical environment, which can be null .

Simply put, a lexical environment is a structure that stores information about the correspondence of identifiers and variables. The “identifier” here means the name of the variable or function, and the “variable” refers to a specific object (including a function) or a primitive value.

In the lexical environment there are two components:

  1. Record environment. This is the place where declarations of variables and functions are stored.
  2. Link to the external environment. The presence of such a link indicates that the lexical environment has access to the parent lexical environment (scope).

There are two types of lexical environments:

  1. A global environment (or global execution context) is a lexical environment that does not have an external environment. The reference of the global environment to the external environment is represented by the value null . In the global environment (in the environment record), built-in entities of the language (such as Object , Array , and so on) are available, which are associated with the global object, and there are also user-defined global variables. The value of this in this environment indicates a global object.
  2. A function environment in which, in an environment record, variables declared by the user are stored. A reference to the external environment can indicate both a global object and a function external to the function in question.

There are two types of environment entries:

  1. A declarative environment record that stores variables, functions, and parameters.
  2. An environment object record that is used to store information about variables and functions in a global context.

As a result, in the global environment, the environment record is represented by the object record of the environment, and in the environment of a function, by the declarative record of the environment.

Notice that in the function environment, the declarative environment record also contains the arguments object, which stores the correspondences between the indices and the values ​​of the arguments passed to the function, and information about the number of such arguments.

The lexical environment can be represented as the following pseudocode:

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

Environment variables


A variable environment (Variable Environment) is also a lexical environment, whose environment record stores bindings created by variable declaration commands ( VariableStatement ) in the current execution context.

Since the environment of variables is also a lexical environment, it has all the above described properties of the lexical environment.

In ES6, there is one difference between the components of LexicalEnvironment and VariableEnvironment . It is that the first is used to store the declarations of functions and variables declared using the let and const keywords, and the second is only to store the bindings of variables declared using the var keyword.

Consider examples that illustrate what we have just discussed:

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

A schematic representation of the execution context for this code will look like this:

 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> } } 

As you probably noticed, variables and constants declared using the let and const keywords do not have values ​​associated with them, and variables declared using the var keyword are assigned the value undefined .

This is due to the fact that during the creation of the context, the code searches for declarations of variables and functions, while the function declarations are stored entirely in the environment. The values ​​of the variables, when using var , are set to undefined , and when using the let or const remain uninitialized.

That is why you can access variables declared with var before declaring them (although they will be undefined ), but when you try to access variables or constants declared with let and const before they are declared, an error occurs .

What we have just described is called “raising variables” (Hoisting). Variable declarations “rise” to the top of their lexical scope before performing operations of assigning them any values.

â–Ť Code execution stage


This is perhaps the easiest part of this material. At this stage, the values ​​are assigned to variables and the code is executed.

Note that if during the execution of the code, the JS engine cannot find the value of a variable declared with the let keyword in the place of declaration, it will assign this variable to undefined .

Results


We have just discussed the internal mechanisms for executing JavaScript code. Although in order to be a very good JS developer, knowing all this is not necessary if you have some understanding of the concepts described above, this will help you better and more deeply understand other mechanisms of the language, such as elevating variables, scopes, closure

Dear readers! What do you think about what else, besides the execution context and the call stack, is it helpful to know JavaScript developers?

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


All Articles