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:
')
- Global execution context. This is the base, default execution context. If a code is not inside a function, then this code belongs to the global context. The global context is characterized by the presence of a global object, which, in the case of the browser, is the
window
object, and by the fact that the keyword this
points to this global object. A program can have only one global context. - Context of the function. Each time a function is called, a new context is created for it. Each function has its own execution context. The program can simultaneously contain multiple contexts of execution of functions. When creating a new context for executing a function, it goes through a certain sequence of steps, which we will discuss below.
- The execution context of the
eval
function. The code executed inside the eval
function also has its own execution context. However, the eval
function is used very rarely, so here we will not talk about this execution context.
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 StatusWhen 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:
- The value of
this
determined and the binding of this
(this binding) is performed. - A
LexicalEnvironment
component (lexical environment) is created. - 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:
- Record environment. This is the place where declarations of variables and functions are stored.
- 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:
- 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. - 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:
- A declarative environment record that stores variables, functions, and parameters.
- 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?
