Let's visually understand how JavaScript works behind the scenes.
When JavaScript runs in the browser it needs to be converted to the machine code as it cannot directly understand JavaScript. When the browser's JavaScript engine encounters JavaScript, it creates a special environment where it handles the "translation" and execution of the code we wrote. This environment is called the execution context.
Execution context can have a global scope and function scope. The very first time JavaScript starts running, it creates the global scope.
Next, the JavaScript is parsed and saves the variable and function declarations into the memory which will be used later to execute different functions.
Finally, the code gets executed where the variables saved in the memory are used.
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 the 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. This variable doesn't come along with its value yet, only the declaration.
In this variable object environment, the value assigned to the variable is undefined.
When we create variables with the keyword let and const the process is slightly 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.
Next, 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 saving the entire function, the execution context saves the 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.
The function execution context is created based on what the functions hold and each function execution context has its 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.
The type of behavior is called lexical scope. This doesn't work backward. 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. 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, outside of everything, the this keyword will refer to the global window (the environment where the function was defined).
What do you think will be the output for each function?
All functions will log the sameā-āapple because they refer to the global window object where our fruit is located. In other words, fruit, window.fruit, and this.fruit refers to the window object.
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 and the rest apple. 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.You might think that the fruit property ("orange") is a variable however we attached it to the object as a property and when we try to log it, it's looking for a variable, not for the property.
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.
Let me remind you one more time how the execution context works. When JavaScript is parsed the 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 functions or objects. If I have 5 functions, I have 5 function execution contexts. All these execution contexts are piled up into an execution stack.
JavaScript is a single-threaded language meaning 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 first.
Then comes another active one and it gets executed next 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 the global scope or the 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.
š Did this help you in any way? Please let me know
Top comments (0)