If you are or want to be a JavaScript developer, or are learning javascript, then you must know how the JavaScript programs are executed internally.Understanding of the execution context and execution stack is vital in order to understand how and why javascript works the way it does. Every javascript piece of code internally uses these concepts and knowing them well will surely make you a much better JavaScript developer.
Execution Context:
There is always a context present with javascript in which your code is being executed. Every block of code will have its own context in which it is executing. Simply put, an execution context
is nothing but an environment where the Javascript code is evaluated and executed. Whenever any code is run in JavaScript, it’s run inside an execution context.
There are 3 different types of execution context in JavaScript:
Global Execution Context (GEC):
This is the default or base execution context. The code that is not inside any function is in the global execution context. It performs two things: it creates a global object which is a window object (in the case of browsers) and sets the value of_this_
to equal to the global object. There can only be one global execution context in a program.Function Execution Context (FEC):
Every time a function is invoked, an execution context is created for that function. The execution context is destroyed when the associated function has returned or execution is over. Each function has its own execution context, but it’s created when the function is invoked or called.Eval Execution Context (EEC):
code inside aneval
function also has its own execution context. But since it is now deprecated & also not recommended, we shall not discuss it.
Below is a sample code snippet demonstrating the execution context.
/* global execution context */
// every execution context has it's own variable environment
// GEC: global execution context
// FEC: function execution context
function baz(){
// FEC of baz
var foo = 3;
console.log(foo); //3
}
function bar(){
// FEC of bar
var foo = 2;
console.log(foo); //2
baz();
}
var foo = 1; //GEC
console.log(foo); //1
bar();
console.log(foo); //1
/* global execution context */
- When the program runs, the first thing the javascript engine creates is a
global execution context
. Everything that is not inside a function is a part of the GEC. So the varfoo
is in the GEC & is put into memory space. - When the javascript interpreter comes across
bar()
,the function is invoked, a new execution context is immediately created and everything inside of it starts executing. - Now this is the most important point to remember:
'Every execution context (FEC) has it's own variable environment'. Therefore when the variable
foo
is declared again with value 2, it is created only within bar()'s execution context. - Then again
baz()
is invoked, and a new execution context with its own variable environment, it's own memory space for its variables is created. Hence, when the variablefoo
is re-declared inbaz()
, it is only limited to this environment, and is different altogether. - In short, every time you call a function, you get your own execution context.
- So even though,
foo
is declared 3 times, they are distinct, they are unique, they don't touch each other. - Therefore calling functions
bar()
andbaz()
wouldn't affectfoo = 1
in the GEC. To prove that, we have consoledfoo
again at the very end of the program after callingbar()
. Should it have an impact? The answer is NO!!.
This is how it is executed internally from execution context's point of view.
Execution Stack:
In computer science, a call stack is a stack data structure that stores information about the active subroutines of a computer program. This kind of stack is also known as an execution stack
, program stack
, control stack
, run-time stack
, or machine stack
, and is often shortened to just "the stack
". Wikipedia
A subroutine in simple words is nothing but what we call a function
. Simply put, JavaScript has a single call stack in which it keeps track of what function we are currently executing & what function is to be executed after that. But first- what's a stack? A stack is an array-like data structure where you can add items(push) to the back and only remove(pop) the last item from it.
Woof!! those are some big words. I know it is difficult to digest the above piece of information, but be patient. Trust me, a piece of code and some visual diagrams will definitely make the concept clearer. So let's examine a piece of code.
// Global execution context
console.log("global execution context");
function foo() {
console.log("foo is executing");
console.log("foo has finished executing");
}
function bar() {
console.log("bar is executing");
foo();
console.log("bar has finished executing");
}
function baz() {
console.log("baz is executing");
bar();
console.log("baz has finished executing");
}
baz();
console.log("program successfully executed");
// Global execution context
// global execution context
// baz is executing
// bar is executing
// foo is executing
// foo has finished executing
// bar has finished executing
// baz has finished executing
// program successfully executed
Initially, before running any piece of javascript code, the execution stack
is empty. When we run a javascript program/file, the javascript engine creates a global execution context
and pushes it on top of the stack the moment your program starts executing.
In simple words, it is the context of your entire javascript program/source code or the context within which your entire code runs(as shown in the code snippet).
Given below is a simple illustration of how it really looks from the execution stack's perspective.
- When the javascript interpreter comes to
baz()
, the point of time whenbaz()
is called, a new execution context is created for baz() and is put on top of the execution stack. - Inside function baz(), statements are executed then (if any). The moment
bar()
is called inside baz(), a new execution context ofbar()
is created and is pushed to the top of the stack. - The interpreter then executes
bar()
and the statements inside of it until it encounters invoking functionfoo()
and a new execution context is created and pushed on top of the stack. - In the above diagram, at any given point:
(i). The running execution context is always on top of the stack.
(ii). There can be at most one execution context running on the stack at a time.
(iii). The arrow(⇨) on the stack denotes the current execution context.
- After
foo()
has finished execution, it is popped off from the stack & the control comes to the next line from where it was invoked i.e frombar()
. - Similarly, the process goes on until each and every execution context gets completed and is removed from the stack.
- Finally, the GEC remains on the stack and is eventually popped off when the program completely finishes executing.
Scope Chain:
What if, an execution context has no definition of a variable which it wants to print? Consider the code snippet below. baz()
has no variable declared as foo
in its execution context.
// every execution context has a reference to it's outer environment
function baz(){
/* foo's value is taken from the outer environment i.e. global environment */
console.log(foo); //1
}
function bar(){
var foo = 2;
console.log(foo); //2
baz();
}
var foo = 1;
bar();
'Every execution context has a reference to it's outer environment', all the way up to the global scope. This hierarchy/chain of reference is what is termed as Scope Chain. So if the current execution context has a variable definition missing, it accesses its outer environment to look out for the variable. In the above code, when the execution context of baz()
couldn't find a variable foo
inside its environment, it went searching for it in it's outer environment i.e. Global execution context
and prints out it's value. This is what happened when the current execution context couldn't find a variable foo
.
The outer environment of baz()
happens to be the global execution context in our example. This is not the case every time. Consider the code snippet below:
// every execution context has a reference to it's outer environment
function bar(){
var foo = 2;
/* here baz's outer environment is the execution context of bar */
/* and not the global execution context*/
function baz(){
console.log(foo); //2
}
baz();
}
var foo = 1;
bar();
Now, we have moved baz()
inside the function bar()
, i.e. it is now nested inside bar(). In this case, it's outer environment has now changed from the global execution context to the execution context of function bar()
. So now instead of printing foo
as 1(from GEC) it prints foo
as 2(from bar's execution context).
Conclusion:
Execution context had always been confusing for developers at first, especially those who are new to JavaScript. Visualizing these concepts and how they work internally helps simplify things making it a piece of cake 🍰. Knowing these concepts will help you know how & why javascript works the way it does. Do let me know if you guys have any queries or feedback for improvement. Hope you like it. Happy learning...😊
Top comments (1)
Learn Javascript Execution Context in Detail! Watch this video and master the concept in no time. Get the best and most comprehensive knowledge on Execution Context now!