Execution Context and the Call Stack. How does Javascript run each line of your code?
Introduction
Have you ever wondered how each line of your Javascript code gets executed regardless of how long it may be? In this article, I will be discussing how exactly it happens fundamentally because as Javascript developers, we need to understand it so that we can write better code.
Execution Context and Call Stack
We know that Javascript code is executed in the Call Stack and I will go into detail in this article.
Let us start our discussion from the point where Javascript code finishes the compilation process, just the way I wrote about it in my previous article. What happens is a global execution context is created for top-level code (code that is not inside any function) and so only these functions will first be executed.
Using the code snippet as a case study, let us see how exactly it works.
let a = 2;
const b = 3;
const details = () =>{
const c = profile(1, 3);
a = a + b + c;
return a;
}
function profile(x, y){
const e = 4
return x + y + e;
}
const getDetails = details();
The a and b variables will be executed first in the global execution context because they are not inside a function. The other two functions, that is, the details() and profile() functions will also be declared to be called later. The code in these functions will only be executed whenever these functions are called.
EXECUTION CONTEXT
We proceed to understand what exactly the term “Execution Context” is.
It is essentially an environment where JavaScript code is executed. It is a store for all necessary information for code to be executed. The necessary information could be arguments passed into a function call or some variables and so on.
So, in any Javascript block of code, regardless of how large it may be, there is only one execution context. Execution simply means the CPU processing the machine code that it receives.
As soon as the top-level code is executed, other functions begin to be executed.
How this works is that for every call of a function, a new execution context will be created containing all the necessary information to run that particular function. The same thing applies to methods because they are functions attached to objects.
When all the functions are executed, the engine will keep waiting for callback functions to arrive so that it can execute them. For example, a callback function associated with a “submit” event, (remember that the event loop provides these new callback functions as I wrote in my previous article).
What Is Inside The Execution Context?
1. The Variable Environment: The first thing inside the Execution Context is the variable environment. The let, const and var declarations, functions, and arguments object are stored here. The arguments objects are the arguments passed into a function that belongs to the current execution context. Do not forget that we just learned that each function gets its execution context. Therefore, all the variables declared inside a function will end up in its variable environment. However, variables declared outside a function can be accessed by that function because of something called the “scope chain”. This is the second thing inside the execution context.
2. The Scope Chain: The Execution Context also contains the scope chain which consists of references to variables outside the current function. The scope chain is stored inside the execution context so that it can be properly tracked.
3. The ‘this’ keyword: The last thing inside the Execution Context is the ‘this’ keyword. It allows access to properties and methods within that particular execution context.
The aforementioned three things contained within the execution context, that is, the variable environment, scope chain and ‘this’ keyword are created during something called the creation-phase which happens right before execution.
One important thing to note is that Execution Contexts belonging to arrow functions do not get their own arguments keyword, nor do they get the ‘this’ keyword. The arrow functions do not have arguments object and the ‘this’ keyword. They use arguments object and the ‘this’ keyword from their closest regular function parent.
All these are necessary to run each function and top-level code in our Javascript code.
This leads us to the Call Stack which is a place where the execution contexts get stacked on top of one another to keep track of where we are in the program’s execution. This means that the execution context at the top of the stack at any point in time is the one that is currently running and when it is done running, it gets removed from the call stack.
let a = 2;
const b = 3;
const details = () =>{
const c = profile(1, 3);
a = a + b + c;
return a;
}
function profile(x, y){
const e = 4
return x + y + e;
}
const getDetails = details();
The code above gets executed like so: the variables a, b and the functions details() and profile(), and the getDetails() function all go into the global execution context because they are top-level code. Below is an illustration of the Call Stack with the global execution context inside it:
Now, it gets exciting when we get to the variable getDetails, we declare it with the value that will be returned from calling the details() function.
What happens when the details() function is called? It gets its own execution context so that the code inside its body can run.
What then happens to this execution context created for the details function? It gets placed into the call stack on top of the current context which is the global execution context. The execution context for the details() function then becomes the current execution context.
After this, the profile function gets called within the details function and a new execution context is created for it and placed in the call stack as the current execution context. This leads to a pause in the execution of the details() function. Only the profile() function is currently being executed. It is only after the profile() function is completely executed that the details function resumes execution because remember, Javascript is single-threaded and can only do one thing at a time.
When x + y + e is returned during the execution of the profile() function, the profile function execution context gets removed from the call stack. The previous execution context, that is, the details() function will now be back to being executed and then we will have the illustration below again:
After getting to the return statement inside the details() function, it completes being executed and is also removed from the call stack, leaving us with only the global execution context:
The Call Stack will always remain like this unless there is an action to totally end execution within the Call Stack like closing the browser tab or closing the browser window. If this happens, the global execution context then gets removed from the call stack.
Conclusion
This is summarily how the Execution Context and Call Stack work and I hope this article has given you a better understanding of how your Javascript code runs line by line. Please leave a comment in the comment section.