Sep 14, 2021
#javascript
6 min read
last updated: Sep 17, 2021
So, this is our HTML file.
<!DOCTYPE html> <html lang="en"> <head> <!-- meta tags -- > <title>JS Playground</title> <script src="app.js" ></script> </head> <body> </body> </html>
We've got the HTML shell, and a script "app.js", which looks like:
// app.js
Yep, it's blank. But guess what, the JS engine has already started working.
The moment the script gets loaded, the engine gets to work, and the first thing it does is: Create an Execution Context.
Context basically refers to the space/environment in which the JS engine evaluates and executes a piece of code. (quite literally how we phrase, "in the context of")
JS engine reads our code line by line, weaves through it like a single thread and executes it, that's why we call JavaScript a single-threaded, synchronous programming language.
π₯ Execution contexts are of following three types:
In the above scenario, with a blank script, we get the default execution context, i.e. the Global Execution Context.
π₯ Now, there are two phases while creating an execution context:
In this phase, we go another level deep into something called a Lexical Environment.
In this environment, JS does a spell, called Hoisting. It scans through our code line-by-line and captures all the variable and function declarations, allocates them some space in the memory and initializes them with some values.
What those values are?
For primitive values, it's
For functions, the entire function definition is stored
π₯ Going deeper, we've got three primary components under lexical environment:
π Environment Record
Now environment record in itself has two types: Declarative and Object. You can read more here.
All in all, it basically keeps a record of all the variable and function declarations. This record is a reflection of "Hoisting" we've briefed above.
π Outer environment reference
As your code grows, so does the variables, functions and nesting. So, JS makes sure that every Execution Context has a reference to its surrounding, in case some variable is outside its own scope (we'll discuss in later posts).
let item = "Tesla"; function elonSays() { console.log(`Go buy a ${item}`); }
"elonSays" function has reference to its outer lexical environment. And since the "item" variable is not in function scope, so JS knows to look for the "item" variable in the outer environment which happens to be the global context in this case.
*For a function, the outer reference will be the "Global Context", however for the global context itself, it will be "null" since nothing sits over it. *
π "this" keyword binding
Now, the "this" keyword need separate discussion altogether. Let's keep it simple for now.
"this" keyword refers to an object. Now what that object is, depends on the context. Consider this π example:
// 1. global context console.log(this); // displays the "window" object // 2. function called in global context function whatIsThis() { console.log(this); // displays the "window" object } // 3. function as an object property let agent = { name: "Jason Bourne", showPosition: function () { return this; }, }; console.log(agent.showPosition); // displays the "agent" object
All those hoisted variable will be assigned corresponding values, and the function(s) will be executed.
var character = "Draco Malfoy"; function greetCharacter() { console.log(`Hello, ${character}`); } greetCharacter(); // upon hoisting // var role = undefined; // entire greetWorld function definition stored in memory // during execution phase // var role = "Draco Malfoy"; // executes the function and prints "hello world"
let characterOne = "Sirius Black"; showFavoriteCharacter(); function showFavoriteCharacter() { let characterTwo = "Severun Snape"; console.log(`Everyone loves ${characterOne} and ${characterTwo}`); }
Global Execution Context
Creation phase
let characterOne; // uninitialized
showFavoriteCharacter //holds the entire function definition
Execution phase
Now, as we hit the "showFavoriteCharacter" function, a totally new execution context will be created.
Function Execution Context
Creation phase
let characterTwo; // uninitialized
Execution phase
let character = "Severus Snap";
With that outer reference, the function is able to locate the "characterTwo" variable in the global context. And finally, it logs: "Everyone loves Sirius Black and Severus Snape"
This whole process of creation and execution occurs for each and every function that gets called throughout your script, no matter how nested it gets.
Β
π€ But, wait a minute. How does JavaScript handle those bazillion execution contexts, that too with deep nesting?
π₯ Well, JS maintains the flow of execution using the Execution Call Stack, or simply Call Stack. A stack basically follows the LIFO principle i.e. Last In First Out.
The process goes like this:
outer(); function outer() { //some instructions inner(); function inner() { // some instructions } }
During the execution phase, the call stack would look like this,
[2] inner()
[1] outer()
[0] GEC
Once each function execution finishes, its FEC popped off the stack, and ultimately the GEC is removed as well.
And there you go. Hope you got some clarity on how the execution stack is created and handled.
Β
ππ‘ Resources referenced for this blog:
Β
πTeaser
Did you see how we've called outer() function even before defining the function itself. Well, it's all because of Hoisting and Scope. Topics that we'll discuss in the upcoming posts.
Β
Until then, if you have any queries, suggestions or corrections, drop a comment below.
Have a good one. Take care. Bye. π