JavaScript Execution Context

This subject holds great significance for individuals working as JavaScript developers or seeking a comprehensive understanding of JavaScript's functionality.

In this part of the tutorial, we are going to delve into the concept of the execution context in JavaScript. We will explore its definition, various types, the execution stack, the process of creating an execution context, and delve into the details of the execution phase. Each of these aspects will be covered individually. So, let's commence with an overview of the subject.

What actually is an Execution Context?

The term "execution context" refers to how a piece of code operates internally. In the realm of JavaScript, the setup that allows JavaScript code to run is referred to as the JavaScript Execution Context. This context determines which parts of the code can interact with the functions, variables, and objects utilized within the code. When an execution context is initiated, the code is analyzed sequentially, and the variables and functions are stored in memory. Think of an execution context as a storage unit for variables where the code is assessed and subsequently run. Essentially, the execution context establishes the conditions necessary for a particular piece of code to be executed.

Types of Execution Context

The types of execution context in JavaScript are:

  • Global Execution Context/GEC
  • Functional Execution Context/FEC
  • Eval Execution Context

Begin to discuss each one by one:

Global Execution Context

GEC / Global Execution Context is also called the base/default execution. Any JavaScript code which does not reside in any function will be present in the global execution context. The reason behind its name 'default execution context' where the code begins its execution when the file first loads in the web browser. GEC performs the two following tasks:

  • Firstly, it creates a global object where it is for Node.js and Window object for the browsers.
  • Secondly, reference the Windows object to 'this' keyword.
  • Create a memory heap in order to store variables and function references.
  • Then it stores all the functions declarations in the memory heap area and the variables in the GEC with initial values as 'undefined'.

Given the introduction provided, it is essential to comprehend that the Global Execution Context in JavaScript is unique for each code execution due to the single-threaded nature of the JS engine, allowing only one global environment for running JavaScript code.

Functional Execution Context

Functional Execution Code (FEC) refers to a specific context established by the JavaScript engine upon encountering a function call. Each function possesses its individual execution context, resulting in the potential existence of multiple FECs as opposed to the Global Execution Context (GEC). Unlike GEC, FECs can be more than one. While FECs have access to the entire code within the GEC, the reverse is not true; GEC cannot access all code within FEC. When a function call is triggered during GEC code execution, the JavaScript engine generates a distinct FEC dedicated to that particular function.

Eval Function Execution Context

Every JavaScript code executed inside the eval function establishes and maintains its unique execution context. Even though JavaScript developers tend to avoid using the eval function, it is an integral component of the Execution Context.

Execution Stack

The execution stack is also known as Call Stack .

The stack operates on the principle of Last In, First Out (LIFO) and serves as a data structure for storing values. Correspondingly, an execution stack is responsible for keeping a record of all the execution contexts formed throughout the lifecycle of a script. It is crucial for JavaScript developers to understand that JavaScript functions as a single-threaded language, meaning it can only carry out one task at a time within a web browser. Consequently, when additional actions, functions, or events are triggered, a stack is generated, referred to as the Execution Stack. Positioned at the base of the execution stack is the Global Execution Context (GEC), which is inherently present.

Upon the initiation of JavaScript code execution (i.e., during GEC execution), whenever a function is encountered in the code, the JavaScript engine promptly generates a Function Execution Context (FEC) for that specific function and places it at the top of the execution context stack. The execution context positioned at the peak of the execution context stack always takes precedence for execution by the JavaScript engine. Once the code execution is completed, the JavaScript engine eliminates the function's execution context from the stack and proceeds to the subsequent one, and so forth.

Typically, when a script is loaded in a browser, the initial element is the global execution context. However, upon detecting a function execution, a new execution context is established and virtually positioned atop the GEC. This cycle persists until the entire code execution reaches its conclusion.

To grasp how the execution stack operates, let's examine the following code example:

Example

let x = 'Hello World!';

function a() {

  console.log('It is the first function');

function b() {

  console.log('It is the second function');

}

b();

}

a();

console.log('It is GEC);

It is an example code to understand its working.

Explanation:

  • Firstly, all the code is loaded into the browser.
  • After it, the JS engine pushes/inserts the GEC at the top of the execution stack.
  • As soon as the JS engine encounters the first function call, it set up a new FEC for it and adds it to the top of the current execution stack.
  • Then we can see that it is the invocation of the second function within the first function. Therefore JS engine setup a new FEC for the second function and insert it to the top of the stack.
  • When the second function is completed, the execution function is popped out of the stack, and controls move to the next execution context present in the stack, which is the first function execution context only.
  • When the first function gets executed completely, the execution stack of the first function popped out from the stack. Hence, the control reaches back to the GEC of the code.
  • At last, when the execution of the entire code gets completed, the JS engine removes the GEC from the current stack.

This is how the execution stack operates during program execution.

Creating an Execution Context

Initially, an execution context is established before being handled. The generation of the execution context is carried out through two methods:

Creation Phase

During the creation phase, the JavaScript engine calls a function without starting its execution. At this stage, the engine initiates the compilation process and analyzes the specific function code for compilation but does not run the code. Generating the Execution Context is the duty of the JavaScript engine, which accomplishes it by carrying out the tasks detailed below:

Task 1: Generating the Activation Object/Variable Object: The Activation object in JavaScript serves as a special container that stores details such as function arguments, variables, and declarations of inner functions. Unlike regular objects, it does not possess the dunder proto property.

Task 2 involves establishing the scope chain after finishing task 1. The scope chain is a collection containing the variable objects where the current function is located. It includes the variable object of the Global Execution Context (GEC) and the variable object of the current function.

Following the creation of the scope chain, the JavaScript engine proceeds to set the value of 'this'.

Let's explore how to create an Activation object using the following example:

Example Code 1

Example

function test (x, y) {

  var z = 10;

  

  var w = 5;

  w = function() {

    return x - y;

  }

}

test(12, 3);

Upon calling the test function, the JavaScript engine generates an executionContextObj for the sample code 1 before proceeding with its execution. This process is illustrated in the following code snippet:

Example

executionContextObj = {

 variableObject: {}, // contains all variables, inner functions, arguments details of test()

 scopechain: [], // contains list of all the scopes for test()

 this // Value of this 

}

The Activation object encompasses the argument object which provides information about the function's arguments. It includes the name of each function property and variables declared within the function scope. In the given scenario, the Activation object for instance, code 1, would appear as follows:

Example

variableObject = {

  argumentObject : {

    0: x,

    1: y,

    length: 2

  },

  x: 3,

  y: 2

  z: undefined,

  w: undefined so the pointer points to the function definition of w

}

Explanation:

  • The JS engine has created the argument object, as you can see in the above code. Also, there exists a length property that contains the total number of arguments in the function. It is having only the property name and not its value.
  • After this, for each variable in the function whose value is initialized with 'undefined', the JS engine setups a property on the activation or variable object. These arguments are also variables in the function, so are also a property of the argument object.
  • If there is a situation where the variable is already present as the argument object property, then the JS engine moves on without taking any further action.
  • When the JS engine finds a function definition within the current function, by using the name of the function, it creates a new property. As discussed above, function definitions get stored in heap memory. The function name property points to its definition in the heap memory.
  • So, we can see in the above code that w is a variable. Therefore, it will get the value of 'undefined'. However, when a function with the same name is found, overriding will take place, and its value will point it to the definition of function w, which is stored in the heap memory. After it, the JS engine setup the scope chain and determine the value of 'this'.

So, in this way, the creation phase works.

Execution Phase

Following the completion of the creation phase, the subsequent phase is the execution phase. During this phase, JavaScript engines review the function in the code to update the variable object with variable values before executing the code. Let's examine the execution stage or the full code of the example discussed earlier:

Example Code 2

Example

x = 10;

var y = 20;

z = function(val) {

  var p = 5;

  var q= 10;

    x = 30

  function test () {

    var r = 2;

  }

 test();

}

z(3);

Initially, the code mentioned above is loaded into the browser. Subsequently, the JavaScript engine initiates the compilation stage to generate the execution objects. During this phase, the JavaScript engine specifically deals with and oversees the declarations exclusively, without concerning itself with the values.

Now, in the execution phase, the following steps will be performed as described per line:

  • Variable x is assigned with 10, which makes the JS engine not to think of it as a function declaration or variable declaration and moves further, i.e., to the third line. In the third line, it does not do anything as it is not any declaration.
  • Next, the JS engine setup a property with 'z' (variable name)in the GEC object because z is a variable name and also in global declaration scope and will initialize it with an 'undefined' value.
  • Moving to the fifth line of the code, a function declaration is encountered by the JS engine. The JS engine will store the function definition in the heap memory and, after that, will set up a property that will point to that specific heap memory location. It does not matter what is stored in the function, it just points to its location.
  • As we can see in the last line, it is not any declaration of the code. Hence, the JS engine will not perform any action.

Consequently, both the creation phase and the execution phase occur in this manner.

GEC object after the creation phase stage

In the previous description, we learned about the establishment of the execution stack during the execution and creation phases in the sample code 2. It is crucial to delve into the functions of the Global Execution Context (GEC) and Function Execution Context (FEC) specifically for the mentioned code. Let's examine the Global Execution Context object code provided below for example code 2:

Example

globalExecutionContextObj = {

activationbj: {

argumentObj : {

length:0

},

y: undefined,

z: Pointer to the function definition

},

scopeChain: [Global execution context variable object],

this: value of this

}

As you can see above, no code is left, the JS engine moves to the execution phase for scanning the function once more. The JS engine updates the value of the variable then execute the code in the following way as described below per line:

  • Firstly, the JS engine finds that no property is there having name x in the variable object, so it adds this property in the GEC and initializes its value as 10.
  • Next, the JS engine finds that a property with the name y in the variable object is present and thus updates its value to 20.
  • At last, the JS engine does not take any action because it is a function declaration.

Next, let's examine the GEC object following the completion of the execution phase.

GEC object after Execution Phase

In our example code 2:

Example

globalExecutionContextObj = {

  activationbj: {

      argumentObj : {

          length:0

      },

      y: 20,

      z: Pointer to the function definition,

      x: 10

  },

  scopeChain: [Global execution context variable object],

  this: value of this

}
  • Now, as z gets called again, the JS engine again enters the compilation phase. Thus, it scans the function for creating the execution context object of it.
  • The function z has 'val' as its argument, so the JS engine will add 'val' in the argument object of the z execution context object. Then it creates a property via the name 'val'.
  • Next, it checks and finds if p is a property in the activation object of the function or not. Hence, it found that there is no such property exists, so it will add p as property then initialize its value to 'undefined'.
  • Next, it is the duty of the JS engine to see if q is a property in the activation object of the function. Hence, it found that there is no such property exists, so it will add q as property then initialize its value to 'undefined'.
  • Then, the JS engine proceeds to the next line as x = 30 is not a declaration.
  • Then, the JS engine encounters a test function declaration, and for it, it stores the function definition in the heap memory area. Then, it set up a property with the name 'test' that points to the location where function definition is stored. The JS engine does not focus on what is the value stored in it.

z execution object after the compilation phase

The following code snippet represents the FEC object following the compilation phase:

Example

zExecutionContextObj = {

  activationbj: {

      argumentObj : {

          0: val,

          length:1

      },

      val: 3,

      p: undefined,

      q: undefined

      test: Pointer to the function definition,

  },

  scopeChain: [z variable object, Global execution context variable object],

  this: value of this

}

The code after the compilation phase of the example code 2 is performed as described below:

  • It is the test function call and not a declaration because of which the JS engine will not do anything.
  • Now, the JS engine will proceed to the execution phase for executing the z function by scanning it.
  • In the execution phase, the variables p and q get the 5 and 10 as their values.
  • Next, the JS engine finds that x is not a declaration and any property on z execution context object, so it moves to the GEC of the code via the scope chain and sees in the GEC any property with the name x exists or not. If not found, the JS engine will create a new property with the name x and will initialize it. In example code 2, the JS engine finds a property with the name x that already exists on the GEC object, so it updates its value to 30 from 10. One should note that the JS engine proceeds to the GEC in this case only, i.e., when it finds a variable in the execution phase which is not a property on the current execution context object
  • After it, the JS engine sets up a test property and then will point to its heap location.

The Execution Context Object of z following the completion of the execution phase.

In example code 2:

Example

zExecutionContextObj = {

  activationbj: {

      argumentObj : {

          0: val,

          length:1

      },

      val: 3,

      p: 5,

      q: 10

      test: Pointer to the function definition,

  },

  scopeChain: [z variable object, Global execution context variable object],

  this: value of this

}
  • In example code 2, once again, the JS engine moves to the compilation phase for creating the execution context object for 'test'.
  • The test execution context object is having access to every function defined on z, on variables, and is also in the global scope using the scope chain.
  • In the same way, z has access to every variable and object in the global scope. However, it cannot access the test variables and objects.
  • The GEC of the code is not having any access to z or test variables or objects.

This is the process by which the execution context is established and defined.

Global Execution Context Vs. Function Execution Context

The variances between the two are as follows:

Global Execution Context Function Execution Context
It creates a global scope. It creates an argument object.
It creates an object known as 'this.' It points to the Window object by default.
It set up memory space for the functions and variables that are globally defined. It set up memory space for functions and variables that are defined within the function only.
The GEC, while setting any function declaration in the memory, assigns a default value as 'undefined' to the variable declaration. The FEC, while setting any function declaration in the memory, assigns a default value as 'undefined' to the variable declaration. With this, it creates its own Execution Stack also.

Input Required

This code uses input(). Please provide values below: