Table of contents
No headings in the article.
JavaScript is a synchronous and single-threaded language. To know behind the scene how JavaScript code gets executed internally, we have to know something called Execution Context and its role in the execution of JavaScript code.
Execution context is the concept for describing the internal working of a code. In JavaScript, the environment that enables the JavaScript code to get executed is what we call JavaScript Execution Context. It is the execution context that decides which code section has access to the functions, variables, and objects used in the code.
During the execution context, the specific code gets parsed line by line then the variables and functions are stored in the memory. An execution context is similar to a container that stores variables and the code gets evaluated and then executed. Thus, it is the execution context that provides an environment for the specific code to get executed.
var number=10; //line1
function add(n) //line2
{ //line3
var result=n+n; //line4
return result; //line5
}
var result1=add(4); //line6
Types of Execution Context:
Global Execution Context/GEC
Functional Execution Context/FEC
Eval Execution Context
GEC / Global Execution Context:
It 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 the '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'.
The Global Execution Context is only one in every code because the JS engine is single-threaded, and thus, only one global environment is possible for executing the JavaScript code.
Functional Execution Context:
FEC is that type of context which is created by the JavaScript engine when any function call is found. Every function has its own execution context, and thus unlike GEC, the FEC can be more than one. Also, FEC can access the entire code of the GEC, but it is not possible for GEC to access all the code of the FEC. During the GEC code execution, a function call is initiated, and when found by the JS engine, it creates a new FEC for that specific function.
Eval Function Execution Context:
Any JS code that gets executed within the eval function creates and holds its own execution context. However, the eval function is not used by the JavaScript developers, but it is a part of the Execution Context.
Execution Stack:
The execution stack is also known as Call Stack. The stack is the data structure that stores the values in the form of LIFO (last in, first out). Similarly, an execution stack is a stack that carries track of all the execution contexts developed during the script life cycle. A JavaScript developer must be known the fact that JavaScript works as single-threaded where it is capable of executing a single task in the web browser at a time. Thus, for other actions, functions, and events, a stack is created and is known as the Execution Stack. At the bottom of the execution stack, GEC resides, and it is present by default in the stack. So, when beginning a JS code execution , when any function is present in the code, and the JS engine searches it, it instantly creates an FEC for that function and pushes it on the top of the execution context stack.
The particular execution context which is available at the top of the execution context stack will always get executed by the JS engine first. As soon as the execution of all the code is done, the JS engine pops out the function's execution context and then moves towards the next and so on. Generally, when the script loads in the browser, the first element will be the global execution context. But when a function execution is detected, the execution context is created and gets virtually placed on the top of the GEC. The process continues until the execution of the whole code gets completed.
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);
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.
In this way, the execution of the execution stack is performed.
Creating an Execution Context:
An execution context is created first, and then it is managed. The creation of the execution context is performed in two approaches:
Creation Phase:
The creation phase is the one in which the JS engine invokes a function but does not begin its execution. In this phase, the JS engine begins its compilation phase and scans the particular function code for compiling it but does not execute the code. Creation of the Execution Context is the responsibility of the JavaScript engine, and it creates it by performing the following described tasks:
Task 1: Creation of the Activation object/Variable Object: One of the special objects of JavaScript that is like a container that holds all the information of the function arguments, variables as well as inner functions declaration. With this, it does not have the dunder proto property.
Task 2: Creation of the scope chain: After completing task 1, the JS engine initializes the scope chain. A scope chain is a list that has all the variables and objects within which the current function exists. A scope chain also has the variable object of the GEC and carries the current function variable object.
- Determining 'this' value: Once the scope chain is created, the value of 'this' is initialized by the JS engine.
Example:
function test (x, y) {
var z = 10;
var w = 5;
w = function() {
return x - y;
}
}
test(12, 3);
Now, the JS engine creates an executionContextObj for example code 1 after the invocation of test () and before executing its code. It can be seen in the below code:
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 contains the argument object that further contains the details about the arguments of the function. It carries the property name for every function and variable that are declared within the current function. In our case, the Activation object, for example, code will be:
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
The execution phase is the next phase after finishing the creation phase. An execution phase is the one where the JS engines scan through the function in the code once again, i.e., one more time for updating the variable object with the values of the variables and then run the code. Let's see the execution stage or the complete code for the example that we have discussed above:
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);
Firstly, the above code gets loaded in the browser. After that, the JS engine begins its compilation phase to create the execution objects. In the compilation phase, the JS engine manages and handles the declarations only and does not care about 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 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 set up a property with 'z' (variable name)in the GEC object because z is a variable name and also in the 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.
So, in this way, the working of both the creation phase, as well as the execution phase, takes place.
GEC object after the creation phase stage
globalExecutionContextObj = {
activationbj: {
argumentObj : {
length:0
},
y: undefined,
z: Pointer to the function definition
},
scopeChain: [Global execution context variable object],
this: value of this
}
GEC object after Execution Phase
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 the function definition is stored. The JS engine does not focus on what is the value stored in it.
Below is the code for the FEC object after completing the compilation phase:
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
}
So, this is how the execution context is created and is defined.
There are the following differences between the two:
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. |