JavaScript Execution Context - How JS Works Behind The Scenes
JavaScript Execution Context - How JS Works Behind The Scenes
Before we dive in, here are some prerequisites to familiarize yourself with,
because we'll use them often in this article.
functiondoSomething()
Function Expressions: These are anonymous functions, that is
{//here"doSomething"isthefunction'sname
functions without a function name like js function () { statements } .
statements;
They are usually used in statements, like assigning a function to a
}
variable. let someValue = function () { statements } .
Now, that we've gotten those out of the way, let's dive in.
The Execution Context contains the code that's currently running, and
everything that aids in its execution.
During the Execution Context run-time, the specific code gets parsed by a
parser, the variables and functions are stored in memory, executable
byte- code gets generated, and the code gets executed.
The GEC is the base/default Execution Context where all JavaScript code
that is not inside of a function gets executed.
Since every function call gets its own FEC, there can be more than one
FEC in the run-time of a script.
1. Creation Phase
2. Execution Phase
Creation Phase
In the creation phase, the Execution Context is first associated with an
Execution Context Object (ECO). The Execution Context Object stores a
lot of important data which the code in the Execution Context uses
during its run-time.
The creation phase occurs in 3 stages, during which the properties of the
Execution Context Object are defined and set. These stages are:
In the GEC, for each variable declared with the var keyword, a property is
added to VO that points to that variable and is set to 'undefined'.
The FEC, on the other hand, does not construct a VO. Rather, it
generates an array-like object called the 'argument' object, which
includes all of the arguments supplied to the function. Learn more about
the argument object here.
Hoisting in JavaScript
Function and variable declarations are hoisted in JavaScript. This means
that they are stored in memory of the current Execution Context's VO and
made
available within the Execution Context even before the execution of the
code begins.
Function Hoisting
In most scenarios when building an application, developers can choose to
define functions at the top of a script, and only later call them down the
code, like so:
However, due to hoisting, the opposite will still work. Where we can call
functions first then define them later down the script.
In the code above, the getAge function declaration will be stored in the
memory of the VO, making it available for use even before it is defined.
Variable Hoisting
Variables initialized with the var keyword are stored in the memory of the
current Execution Context's VO as a property, and initialized with the
value undefined . This means, unlike functions, trying to access the value of
the variable before it is defined will result in undefined .
getAge(1990);
The code execution breaks, because with function expressions, getAge will
bevargetAge=function(yearOfBirth)
hoisted as a variable not as a function. And with variable hoisting, its
{console.log(newDate().getFullYear-yearOfBirth)
value
};
will be set to undefined . That's why we get the error:
Also, variable hoisting does not work for variables initialized with the let or
const keyword. Trying to access a variable ahead of declaration and use
In this case, they will be hoisted but not assigned with the default value of
undefined . js console.log(name); let name = "Victor"; will throw the error:
This means the position of something within a codebase, that is, where a
piece of code is located.
On the right is the Global Scope. It is the default scope created when a
.js script is loaded and is accessible from all functions throughout the
code.
The red box is the scope of the first function, which defines the
variable b = 'Hello!' and the second function.
Now the variables a and b aren't defined in the second function, only c .
However, due to lexical scoping, it has access to the scope of the
function it sits in and that of its parent.
In running the code, the JS engine will not find the variable b in the scope
of the second function. So, it looks up into the scope of its parents,
starting with the first function. There it finds the variable b = 'Hello' . It
goes back to the second function and resolves the b variable there with it.
Same process for the a variable. The JS engine looks up through the scope
of all its parents all the way to the scope of the GEC, resolving its value in
the second function.
Only when the JS engine can't resolve a variable within the scope chain
does it stop executing and throws an error.
However, this doesn't work backward. That is, the global scope will never
have access to the inner function’s variables unless they are returned from
the function.
The scope chain works as a one-way glass. You can see the outside, but
people from the outside cannot see you.
And that is why the red arrow in the image above is pointing upwards
because that is the only direction the scope chains goes.
Once the scope chain is created, the value of 'this' is initialized by the JS
engine.
"this" in The Global Context
In the GEC (outside of any function and object), this refers to the global
object — which is the window object.
Thus, function declarations and variables initialized with the var keyword
get assigned as properties and methods to the global object – window
object.
This means that declaring variables and functions outside of any function,
like this:
Is varoccupation="FrontendDeveloper";
exactly the same as:
functionaddOne(x)
window.occupation="FrontendDeveloper";w
Functions and variables in the GEC get attached as methods and
{console.log(x+1)
indow.addOne=(x)=>{
properties to the window object. That's why the snippet below will return
}console.log(x+1)
true.
};
"this" in Functions
In the case of the FEC, it doesn't create the this object. Rather, it get's access to
that of the environment it is defined in.
Here that'll be the window object, as the function is defined in the GEC:
varmsg="Iwillruletheworld!";
functionprintMsg()
{console.log(this.msg)
;
}
In objects, the this keyword doesn't point to the GEC, but to the object
itself. Referencing this within an object will be the same as:
theObject.thePropertyOrMethodDefinedInIt;
varmsg="Iwillruletheworld!";cons
The code logs "Victor will rule the world!" to the console, and not "I will
tVictor={
rule the world!" because in this case, the value of the this keyword the
msg:"Victorwillruletheworld!",prin
function has access to is that of the object it is defined in, not the global
tMsg(){console.log(this.msg)},
object.
};
With the value of the this keyword set, all the properties of the Execution
Victor.printMsg();//
Context Object have been defined. Leading to the end of the creation
logs"Victorwillruletheworld!"totheconsole.
phase, now the JS engine moves on to the execution phase.
Up until this point, the VO contained variables with the values of undefined
. If the code is run at this point it is bound to return errors, as we can't
work with undefined values.
At this stage, the JavaScript engine reads the code in the current
Execution Context once more, then updates the VO with the actual values
of these
variables. Then the code is parsed by a parser, gets transpired to
executable byte code, and finally gets executed.
When scripts load in the browser, the Global context is created as the
default context where the JS engine starts executing code and is placed at
the bottom of the execution stack.
The JS engine then searches for function calls in the code. For each
function call, a new FEC is created for that function and is placed on top
of the currently executing Execution Context.
The Execution Context at the top of the Execution stack becomes the
active Execution Context, and will always get executed first by the JS
engine.
As soon as the execution of all the code within the active Execution
Context is done, the JS engine pops out that particular function's
Execution Context of the execution stack, moves towards the next below
it, and so on.
first();
After it, the JS engine creates the GEC and places it at the base of the
execution stack.
The same process occurs for the first , second , and third functions.
Don't get confused as to why they functions are still in the GEC.
Remember, the GEC is only for JavaScript code (variables and functions)
that are not inside of any function. Because they were not defined
within any function, the function declarations are in the GEC. Make sense
now 😃?
When the JS engine encounters the first function call, a new FEC is created
for it. This new context is placed on top of the current context, forming
the so- called Execution Stack .
For the duration of the first function call, its Execution Context becomes
the active context where JavaScript code is first executed.
In the first function the variable a = 'Hi!' gets stored in its FEC, not in the
GEC.
The execution of the first function will be paused due to the single-
threaded nature of JavaScript. It has to wait until its execution, that is the
second function, is complete.
Again the JS engine sets up a new FEC for the second function and places
it at the top of the stack, making it the active context.
The second function becomes the active context, the variable b = 'Hey!';
gets store in its FEC, and the third function is invoked within the second
function. Its FEC is created and put on top of the execution stack.
Inside of the third function the variable c = 'Hello!' gets stored in its FEC
and the message Hello! Victor gets logged to the console.
Hence the function has performed all its tasks and we say it returns . Its
FEC gets removed from the top of the stack and the FEC of the second
function which called the third function gets back to being the active
context.
Back in the second function, the message Hey! Victor gets logged to the
console. The function completes its task, returns , and its Execution
Context gets popped off the call stack.
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.
And lastly, when the execution of the entire code gets completed, the JS
engine removes the GEC from the current stack.
GLOBAL EXECUTION
Function Execution Context
CONTEXT
The Execution Context (GEC and FEC), and the call stack are the
processes carried out under the hood by the JS engine that let our code
run.
concepts.
Hope you found this article helpful. Do share it with your friends and
network, and feel free to connect with me on Twitter and my blog where I
share a wide range of free educational articles and resources. This really
motivates me to publish more.
freeCodeCamp.org © 2022