You ready 😉
In the early days of the web (yeah, stone Age 😉), there were ways to create web pages(HTML was released in 1993), but there was no effective way to manipulate them. As we can these days with DOM (Document Object Model - will be discussed later). So those static pages were usually non-interactive. This was the major reason some language needed to be developed to give them life.
At first, It was developed for Netscape 2 and became the ECMA-262 standard in 1997. In 1997, ECMAScript 1 was released and was supported in Internet Explorer 4 for the first time. Then in 1998, ECMAScript 2 came out, and ECMAScript 3 in 1999. This fourth edition of the language was a bit delayed and was released in 2008 but could not make it to market. The fifth major edition, ECMAScript 5 was released in 2009. This version was used for a while and the latest version ECMAScript 6 was released back in 2015, which is now widely supported in all major browsers with and exception the of Internet Explorer. Here you can learn more about the history of your favorite language.
Here ECMAscript is
You cannot possibly learn the language if you do not know how that Language works under the hood. Let's first understand a few important concepts we will be using a lot in this article.
varcan store any data type, like int, string, or object, unlike other statically typed languages like C, and C++ in which we have to explicitly specify variable type
The Runtime Environment is a special environment that provides our code access to built-in libraries, APIs, and objects to our program so it can interact with the outside world and get executed to full fill its noble purpose.
In the context of a web browser, the runtime environment consists of following components:
- The Callstack or Execution Stack
- The Heap
- Web Apis (like fetch, setTimeout, DOM, File API)
- The Callback queue
- The Event Loop
A modern browser is a very complicated piece of software with tens of millions of line lines of code. So it is split into many modules which handle different logic.
The rendering engine paints content on the screen. Blink is a rendering engine that is responsible for the entire rendering pipeline including DOM trees, styles, events, and V8 integration. It parses the DOM trees, resolves styles, and determines the visual geometry of elements on the screen.
We will be focusing on the V8 engine in this article. Read what is V8 Engine from Google's words;
The first version of V8 was released and used by the Google Chrome team in 2010 when they had some problems displaying Google Maps. Later on, they improved it over time and released another version of V8 in 2017, which is being used by Chrome browser these days.
The current version was built on this model;
Let's break it down;
V8 engine downloads JS source code and passes it to Baseline Compiler which process and compiles code to lightweight ByteCode.
This bytecode gets passed to an interpreter which is basically an IntelliSense algorithm that knows what JS code dos what. It interprets the bytecode to CPU understandable binary code.
At the very same time, Bytecode gets passed to the Optimization compiler which optimizes this ByteCode in the background while the application is running. It produces a very optimized binary code, which is eventually replaced with the code in the application, thus giving massive performance and boost.
This is the final model of the V8 engine. But this was not always like this. After many updates and remodeling, this very optimized version of the engine came into existence.
Consider this eternal loop;
The heap is the unstructured memory storage where variables and objects get stored during program execution. Then the heap, also known as the "memory heap" gets cleaned during garbage collection. I will not talk about it in detail, you can check out this awesome article.
We are interested in the call stack, which is basically a LIFO (first in, last out) data storage where current executing contexts are stored while the program is running. We will discuss what Execution Context is in detail in a while. Each entry in the call stack is called a Stack frame. A stack frame contains information about the Execution Context, like its argument object, return address, local variables, etc.
During the Execution context runtime, specific code gets parsed by a parser, variables and declarations get stored in memory(VO), and executable bytecode gets generated which is then converted to binary code, which gets executed.
Two kinds of execution contexts are created during runtime;
- Global Execution Context(GEC)
- Functional Execution Context(FEC)
When the JS engine receives some script file, a default Global execution context is created to handle the code at the root of that file, everything which is outside of any function.
This is the main/default execution context, that encapsulates all of the functional execution contexts.
There is only one GEC for any script file.
When the Global execution context encounters a function invocation, a different kind of execution context, very specific to that function gets created which handles all the logic inside that function.
As we already know that execution context has two major tasks, it prepares the script and then executes it. So we will try to understand the execution context in two phases;
- Creation Phase
- Execution Phase
The creation phase of any execution context completes in three main steps.
- Creation of Variable Object
- Creation of the scope chain
- Assignment of
For GEC a variable object is created which is basically a memory container, which stores properties for all variables and function declarations and stores references to them. When a variable is encountered in a global execution context property is added to the Variable Object and is initialized(in case defined with
var) with the default value
When a function declaration is encountered, a property is added to the Variable Object, and a reference to that function is stored as a value.
This means before even the start of execution of code, variables and function declarations are available to use. Due to hoisting. We will discuss hoisting in great detail in a while.
In case of, FEC (functional execution context) doesn't create a VO but an array-like object called the argument object. All the arguments received by a function are stored in this array-like object.
Variables declarations are also hoisted. Variables declared with the var keyword are hoisted and initialized with a default value undefined. Mean if you will try to access a variable declared with
var, you will not get an error but the value will be
If you declare a variable with a
const keyword, their declaration is hoisted but not initialized with a default
undefined value. That's why you will get an
uncaught ReferenceError, when we try to access it before the declaration.
Here one thing is really important to discuss. You might be asked in an interview about this.
Starting from the top of the scope, until the complete initialization of a variable, that variable is said to be in Temporal Dead Zone(TDZ). You should always try to access variables outside of its TDZ. If you try to access the variable from inside its TDZ, you will get ReferenceError. Here the question arises of where TDZ starts, and where it ends.
See the code below, you will know Temporal Dead Zone starts at the start of the code block(top of scope) and ends at initialization. Variables declared with
const follow the same pattern.
In the case of variables declared with
var the scenario is a bit different. And a guilty guy is hoisting. As we already know variables declared
var are hoisted and initialized at the same time with a default value
undefined. And we also know TDZ ends when a variable has been assigned a value.
This is the reason
var behaves a little differently compared to
const. See this
Hopefully, TDZ is all clear to you. Let's get back to hoisting.
There is one strict rule of hoisting, that hoisting only works for statements, not expressions. Have a look at this code;
This is because we are assigning a function expression to a variable as a value. As we all know variables declared with let keyword are hoisted, but throw
ReferenceError if called within Temporal Dead Zone. This is because their value is not initialized during hoisting and TDZ only ends after the complete initialization of the variable.
Quiz Time: Here is a small question for you? What would be the output if we used var instead of letting in the above code example. Can you guess whether any error will be thrown or function output will be logged to the console?
I am sure you guessed it right. The error will be thrown in this case stating something like myFunc is not a function, because at this stage value of the
myFunc variable will be
undefined due to Hoisting. So calling
undefined() will throw an error.
First of all, when the script is loaded in the engine, global scope is created, which basically holds everything which is not inside any function. All the functions and their inner functions can access this global scope.
When a function is defined in another function, the inner function has access to the code defined in that of the outer function, and that of its parents. This behavior is called lexical scoping.
Whenever a variable or function is called somewhere, the engine starts looking for an inside the local scope, where it was called. If not found, it looks for parent scopes (Lexical Scoping) one by one, from inner(local) to outermost (global scope). If the variable was not found in local and all the parent scopes including the global root scope, then the engine throws an error.
Every function execution context creates its scope which determines what variables and functions are accessible where. There are a few cases that we need to discuss to completely understand scoping.
Case 1: Variables declared inside a function can be accessed from anywhere inside that function, except for Temporal Dead Zone.
When a function inside another function is called outside of its context, means outside of the parent function in which it was declared but it still has access to variables of the parent function even after the parent function has finished execution, this associative phenomenon is called closures. This is a very important concept and you definitely should check it out here.
Parent function has no access to anything declared inside of its child functions. The scope is like a one-way mirror, which means you can look outside but now one can look inside.
As you can see in this image, the function
second has access to all scopes including its own
local scope, the scope of function
first, and the
global scope. But function first has only access to its local scope and the global scope. You can see it has no access to the scope of function
The second step of the creation of the Execution context completes here.
Till now Variable object has been created, scope chain is in place. Let's discuss the third and final step.
The next and final step in the creation of execution context is setting the value of
this is different, depending on the context it is being used. Have a look at these cases;
Case 1: In Global Execution Context,
this refers to the global window object in the case of browsers. Try logging "this" to the console inside GEC and you will see a
window object. Here is one interesting thing to notice. When you define a variable or function inside GEC, they are saved as a property of the window object. This means these 2 statements are equivalent;
And this will log true to the console;
Case 2: Inside FEC, a new
this object is not created, but instead, it refers to the context it belongs to. Like, in this case,
this refers to the global window object. As this function is declared in GEC, this would refer to the global
Case 3: In the case of objects, using
this inside the methods does not refer to the global object, but the object itself. Look at this example;
Case 4: Inside constructor functions,
this refers to the newly created object, when called with
new keyword like this;
With this, the creation phase of Execution Context has been completed. Till now, everything has been stored in VO, the scope chain is created and the value of this is in place.
Initially, this GEC is the active execution context. When some function is encountered in this GEC, and new Functional Execution Context is created for the execution of that function. That FEC holds all the information about the execution of that function and everything that aids in its execution. This newly created execution context is placed right above the GEC. This process is repeated for every function call and FEC is added and piled up in the so-called Callstack.
Execution context on the top is executed first, and once that context is executed (something returned), it is popped out of the stack. The very next context becomes an active one and its execution starts. This process is repeated until there is the last GEC left in the stack. It is executed at the end and the script is said to be executed at this point.
et's sum up all of this by an example. We try to recall everything we have learned so far in this article.
Consider this program inside the script file;
First of all this
Execution Stack. Global Execution Context is created during two phases; the creation phase, and the execution phase.
name="Victor" is stored in the Variable Object (VO) of GEC and initialized with the default value
Then for all these functions
third, a property is added to VO of GEC, and reference to these functions is stored as value. Hoisting you know 😉.
After setting the VO of GEC, the scope chain is created, and value is
this set (
Now starts execution. But the value of
name variable is still
undefined in the VO. And we cannot work with undefined, Right? So once again JS engine looks through VO and feeds the original value of
name variable, which is
Victor. Now we are good to proceed further in our program.
First of all, the function
first gets invoked. JS engine creates a Functional Execution Context to handle its execution. This FEC is placed on top of the Global Execution Context, forming a Callstack or Execution Stack. For the period of time, this FEC is the active one, as we already know Execution Context on the top in Callstack is active. Variable
a = "Hi!" gets stored in the FEC, not GEC.
In the next statement, function
first invokes function
second. Another FEC is created and placed on top of the FEC of function
first. Now this FEC is active. Variable
b="Hey!" gets stored in the FEC.
second invokes function
third, similarly, FEC is created, and placed on the top of the Execution stack. Variable
c = "Hello!" gets stored in the FEC.
So far Execution Stack looks like this;
Hello Victor to the console. But wait! Where does Victor come from? Variable
name is not defined in the function
third. You guessed it right, Scope Chain. The function
name in global scope and finds it.
third has completed all of its purposes, its FEC gets popped out of the Execution stack(Callstack).
Then the very first FEC below it becomes active context and starts execution. Logs
Hey! Victor to the console' and pops out of the Execution stack. Now the last FEC of this program becomes active, logs
Hi! Victor to the console. After execution of all of the statements, it is destroyed and pops out of the Callstack.
We are again left with only GEC in the Execution stack. As well it has nothing left to execute it also pops out of the stack.
Types of execution context; like Global Execution Context(GEC) and Functional Execution Context(FEC).
The creation phase of Execution Context, which completes in three phases; Creation of VO, Scope chain building, and the setting value of this.
The execution phase of Execution Context, we learned how GEC is created once the script is loaded and how every function creates its own FEC. They keep stacking on one another unless they return something and get popped out of Stack.
Scoping and Temporal Dead Zone, we learned how functions can access declarations from their parent scope via Lexical Scoping. We briefly discussed TDZ as well.
This is where Web APIs, Callback queue, and Eventloop come to the rescue. My plan was to discuss all of these concepts in a single guide, but the length grew more than I expected.
- Web APIs
- The Callback Queue
- The Eventloop
I will be dropping another guide like this on these left components. If you want to get notified of the next part, follow me 😉.
While writing this guide, I found really awesome articles you should also check. These articles helped me write this extensive guide.
If you liked this guide and want to get notified of my next articles like this, please do follow me. If your friend is struggling with these concepts, do share this guide.
Till then, Stay safe, and try to keep others safe.
See you soon💓