When the JavaScript runs in the browser it needs to be converted to the machine code as it cannot directly understand it. When the browser’s JavaScript engine encounters JavaScript, it creates a special environment for it where it handles the “translation” and execution of the JavaScript code we wrote. This environment is called the execution context.
In this article, I am going to visually explain how it works as precisely as I can.
Execution context can have global scope and function scope. The very first time JavaScript starts running, it creates a global scope.
Next, the JavaScript is parsed and saves the variable and function declarations into the memory which will be used later on to execute different functions.
Finally, the code gets executed where the variables saved in the memory are used.
Table of Contents:
-Difference between global and function execution context
-How is execution context created?
-Creation phase
-Execution phase
-Conclusion
Difference between global and function execution context
As mentioned above, when the JavaScript engine encounters JavaScript for the first time it creates a default execution context which is global, anything that is not inside a function.
When the engine encounters functions, then it creates another execution context called function execution context where it executes the code located inside the function.
It’s important to note that for every script file, there is only one execution context and so is for function execution context.
How is execution context created?
Execution context has two phases:
Creation phase
Execution phase
Creation phase
The creation phase has 3 stages. During this stage, the execution context is associated with the execution context object which is defined as the properties of this object.
Creation of the variable object
Creation of scope chain
Setting the this value
1️⃣ The variable object
The variable object is an object-like container that stores variable and function declarations within the current execution context.
Every time we create a variable with the keyword var, it’s added to the variable object as a property that points to the variable but it doesn’t come along with its value yet, only the declaration.
In this variable object environment, the value assigned to the variable is undefined. Remember the very first example with the console log? That is exactly why we saw an output of undefined.
When we create variables with the keyword let and const the process is almost the same but technically a little different. Instead of a variable object environment, we have a lexical object environment. The variable object is technically also a lexical environment for specifically the var keyword only.
So what happens with the variables declared with const and let? They are also saved in the lexical environment object but instead of undefined they have a default value of uninitialized.
Finally, if you remember, I have also mentioned that the execution context saves function declarations. Just as the variable doesn’t come along with the assigned values, the same applies to functions. Instead of the entire function, the default value of it is a reference to this function.
When I mention a function, be aware that this doesn’t work with function expressions because we save a function in a variable and it will be saved as a variable whether it’s var, let, or const.
This process of storing variable and function declarations in memory prior to execution is called hoisting which we will discuss in more detail next time.
2️⃣ The scope chain
Scope in JavaScript is something like a box feature that determines whether variables located in it are accessible outside of it. We have scopes like global scope, function scope, and block scope. The variables declared with var are global-scoped and function-scoped, while let and const are function-scoped and block-scoped. I already wrote a post about how variables act in different scopes.
As we already mentioned above we also have function execution context which is created based on what the functions hold and each function execution context has its own scope. Besides, a function in another function can have access to everything in the parent function, even if we have several functions in a function. However, the parent function may not have access to the variables in the child scope if they are function-scoped.
This type of behavior is called lexical scope. This doesn’t work backward. This means that the parent function cannot retrieve variables declared inside its child functions because they are function-scoped.
This link between scopes is called the scope chain. It’s something like a Matryoshka doll aka stacking dolls where in each doll is nested another doll. It means that inner dolls can access the outside doll variable but outside dolls cannot access inside dolls. This means that the scope chain starts from the inner scope all the way up.
3️⃣ Setting the this value
Once the scope chain is done, the next step is setting up the value for this keyword. This keyword refers to the scope of the current execution context.
In an execution context where we have global scope, this refers to the global object window. So anything we declare with var, whether it’s a function or a variable will belong to the window object and be attached as properties or methods.
But what happens in a function execution context?
Function execution context in functions depends on how it is invoked. If it’s invoked in the global context, so a regular way we usually do it, this keyword will refer to the global window, so the environment where it was defined.
What do you think will be the output for each function?
Each of them will console log the same — apple because all of them refer to the global window object where our fruit is located.
But what if we use a function as a method of an object?
In this case, not all console logs will show an apple. The very last one will be orange. Why? Because the this keyword will refer to the inner object context of this object. This means that the current context of the object is separated from the global scope so this refers to the fruit located inside the object myObj.
Execution phase
Once all three phases of the creation phase are done, the JavaScript engine will check the variable object one more time and start assigning the values to continue with the code execution.
Next, let me remind you one more time about the execution context. When the JavaScript is being parsed the very first execution context is created in the global scope. Next, when we create objects or functions, for each function another function execution context is created. So you will have additional function execution contexts, as many as you have functions or objects. So imagine I have 5 functions, which means I have 5 function execution contexts. All these execution contexts are piled up into an execution stack.
Now another important thing about JavaScript, I know it never ends, haha.
JavaScript is a single-threaded language which means that several actions cannot execute at the same.
When function execution context is created it:
goes to the top of the execution context which is currently executing
this execution is on top of the execution stack
and becomes an active execution context
So, as a result, the active execution context is the one that is going to execute the first.
Then comes another active one and it gets executed second and so on.
Note: execution stack is also known as the call stack.
Let’s try to visualize the process
Finally, when all the functions execute the global execution context is removed from the execution context aka the call stack.
Conclusion
In conclusion, understanding how execution context works in JavaScript is crucial for developing effective and efficient code. Each time the JavaScript engine encounters code, it creates a special environment called the execution context, which can have global scope or function scope. During the creation phase, the variable object, scope chain, and this keyword are set up, and in the execution phase, the code is executed. Additionally, the use of var, let, and const for variable declaration, function hoisting, and scope chain determines the accessibility of variables within and outside of functions.
If it’s still very confusing for you, I advise you not to worry too much and slowly try to understand it one by one. I spent hours trying to understand all of this in detail and I still know that I didn’t cover everything and maybe I didn’t explain everything exactly how it works. In any case, it was a great experience to read through such details and I am getting closer!
I also didn’t cover many things in more detail like hoisting however I will get back to it later on.