Loading Inter font-family
Loading Josephine Sans font-family
Loading Hallywood font-family

Smashing JS Like Never: How JS Codes Get Executed (with illustrations)

Showrin Barua
Showrin Barua / March 20, 2022
13 min read----

Smashing JS Like Never: How JS Codes Get Executed (with illustrations) by Showrin

In the programming world, I think the most confusing language is Javascript. Isn’t it? Because it behaves not like other languages. Sometimes it behaves differently from its documentation (It seems at first glance 😅). Ahhh! Just kidding 😛

JavaScript is the world`s most misunderstood programming language. - Douglas Crockford

No programming languages behave differently from their documentation. But when we don’t know “How does a language compile it codes” correctly, then it seems different behavior to us. Every language has its compiling structure. If this structure matches, then two languages work in a similar way as if they were the same. The bad news is Javascript also has its compiling structure that doesn’t match with any other language 🤦‍♂️

This Article Covers

In this article, I’ll try my best to give you a detailed overview of how Javascript code works. After reading this article, you’ll have clear answers to the following questions.

  • Is JS single or multi-threaded; synchronous or asynchronous? 🤔
  • What is Execution Context and what does it do?
  • What is hoisting? Is it only bringing a variable on top of the code? 🤦‍♂️
  • What are the lexical environment and scope chain? Do they have any other duty except hammering devs’ brains? 😡
  • How do code blocks in js get executed?
  • How does JS track which function to call after executing one?

Execution Context

Execution Context is a special environment created by the Javascript engine for executing code blocks. We can define it as a set of specifications that defines what things a JS engine should have while executing a JS code block.

Let me explain it easily. Every work we do or happening surrounding us needs an appropriate environment. For example, to grow a flowering plant, we need enough sunlight, enough water, enough fertilizer, and most importantly the soil. All of them together build an environment. So that’s a proper environment to grow a flowering plant. Did you notice? An environment is nothing but a collection of necessary ingredients to finish a work.

How JS Codes Get Executed -Explained by Showrin Barua

Similarly, to execute a JS code block, we need some ingredients (i.e. values of different global and local variables/constants in JS). Execution context manages all those ingredients in compile-time and makes the compilation happen successfully.

How JS Codes Get Executed -Explained by Showrin Barua

According to the specification of the ECMAScript:

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation - ECMAScript® 2022 Language Specification

A JS engine creates one or more execution contexts (based on the code block) and ensures that each of them has all the necessary data to execute a code block successfully.

An important point to note here is at a time at most one execution context can be active (i.e. can execute the code). And this active context is known as the Running Execution Context.

There are 3 types of the execution context.

  • Global Execution Context (GEC)
  • Function (or local) Execution Context (FEC)
  • Eval Function Execution Context - as eval is not recommended to use, we won’t discuss it in this article

Things to know before the jump

When the JS engine executes any code block, at first, it creates a Global Execution Context (default execution context).

Any execution context (global or local) works in 2 phases.

  1. Creation Phase
  2. Execution Phase

Creation Phase

In the creation phase

  • JS engine scans the code and allocates memory for variables that are used in the code.
    • While allocating memories, for variables, the JS engine takes a memory space and assigns undefined (as value) to it.
    • For functions, the JS engine takes a memory space and stores the whole code to it.
  • It also defines the value of this context.
  • Last but not least, it defines the lexical environment of the context.
  • Here’s an exception for Local (or Function) Execution Context. In this phase, an argument object is being created for this context that stores all the parameters or arguments passed to the function.

Execution Phase

In the execution phase

  • JS Engine executes the code line by line in a sequential manner.
  • Whenever it meets a function call, it creates a function execution context and executes all the code inside that context.
  • To track the sequence of contexts, the JS engine uses a stack called Call Stack or Execution Context Stack.
  • Whenever all the code of a context gets executed, the JS engine pops out the context from the stack and also destroys the context.
  • After finishing the execution of the whole program, it destroys the Global Execution Context as well.

Call Stack (or Execution Context Stack)

Above I said for every function call, the JS engine creates a new Local or Function Execution Context. Now think about a real-life web app like Facebook, Amazon, etc. that have more than hundreds of function call per program. It can be much more. Doesn’t it amaze you, how the JS engine manages the sequence of these contexts? To manage the sequence JS engine chooses a stack known as the call stack. It pushes all the contexts to the stack when they are created. And pops out a context when it’s destroyed. Here’s an illustration to give a clear view of how the call stack works.

How JS Codes Get Executed -Explained by Showrin Barua

Is JS single or multi-threaded?
The answer is Single Threaded as you saw that JS has only one call stack.

Is JS synchronous or not?
The answer is Yes as I said earlier that the JS engine executes the code line by line (i.e. one line at a time).

Let’s Smash the Code Execution Flow

Let’s assume a JS code block. I am going to describe the execution flow of the following code line by line.

1.  var i = 10;
2.
3.  function sqr(num) {
4.	  function printI () {
5.	  	console.log(i);
6.	  }
7.
8. 	  printI();
9.
10.   return num * num;
11. }
12.
13. var a = sqr(i);

Step 1 (Creation of GEC)

Before anything,

  • JS engine will create the Global Execution Context (GEC).
  • Then it will push the GEC to the call stack.
  • In the creation phase, GEC will allocate memory for variables i, a and assign undefined to them.
  • GEC also allocates memory for function sqr and assigns the reference to its code.
  • GEC will set null to its parent’s lexical environment (Lex. Env.) as it doesn’t have any lexical parent.

So after step 1, the whole scenario will be kind of like the following.

How JS Codes Get Executed -Explained by Showrin Barua

What is Hoisting?
As you noticed that JS Engine allocates memory to all the variables before execution. That’s why you can access those variables even before their declaration (not in Strict Mode).

If we write code like this.

1.  console.log(i);
2.  var i = 10;

JS engine will still print i without any reference error. But it will print undefined as 10 is assigned to i later.

This is called Hoisting in JS. So from today, please don’t say that hoisting is bringing a variable on top of the code. Cause, now you know JS well 😎

Step 2 (Assigning Value to i)

In this step, GEC will enter into the execution phase.

  • First, it’ll execute line 1 which is var i = 10.
  • And it’ll assign 10 to the variable i.

How JS Codes Get Executed -Explained by Showrin Barua

Step 3 (Creation of FEC for sqr function)

In this step, GEC will reach line 12. As line 4 - 10 is part of the function sqr, it will need a separate execution context. So GEC won’t execute them.

  • GEC will execute line 12 that is var a = sqr(i);
  • Here a function invocation (call) happened. So JS engine will create a Function Execution Context (FEC 1) for the sqr function.
  • JS engine will push that context to the call stack.
  • Now FEC 1 will allocate memory to variable num and assign undefined to it.
  • FEC 1 will allocate memory to function printI and assign the reference of its code to it.
  • FEC 1 will set the reference of the lexical environment of its lexical parent (GEC) to its parent’s lexical environment (Lex. Env.).

How JS Codes Get Executed -Explained by Showrin Barua

What does Lexical mean? Its vocabulary meaning is “in sequence or in order”. In JS lexical defines where the code is located in the hierarchy.

function a() {
  function b() {
    function c() {
      ......
    }
  }
}

In the above snippet, function c is lexically inside of function b i.e. b is the lexical parent of c. Similarly, a is the lexical parent of b.

What does Lexical Environment mean?
The lexical environment of an execution context is the combination of the local memory and the lexical environment of its parent.
Lexical Environment = Local Memory + Lexical Environment of Lexical Parent

Step 4 (Assigning value to num)

As we were passing i as an argument of the function, FEC 1 will assign 10 to num.

How JS Codes Get Executed -Explained by Showrin Barua

Step 5 (Creation of FEC 2 for printI function)

As lines 5,6 are part of the print function, it needs a separate FEC and FEC 1 will skip them.

  • FEC 1 will execute line 8.
  • As a function call happened again, a separate execution context (FEC 2) will be created.
  • JS engine will push this context to the call stack.
  • There is no variable or function that needs memory allocation. So in the creation phase, nothing will happen except the creation of this object and lexical environment.
  • FEC 2 will set the reference of the lexical environment of FEC 1 to its parent’s lexical environment.

How JS Codes Get Executed -Explained by Showrin Barua

Step 6 (Search for Variable i)

This step is a bit different from others. Cause, till now, every step has all variables or functions they used in their local memory segment. But in this step, the execution context has to print the value of i in the console that is not in its local memory segment. i was defined in the local memory segment of GEC.

How can FEC 2 get the value of i? Will it throw an error or 10 (value of i)?

Here the Lexical Environment comes to play.

How does Lexical Environment help context to find a variable?
Whenever an execution context gets a code statement where access to a variable is needed, it follows some steps.

  • First, it searches for the variable in its local memory.
  • If it doesn’t find the variable here, then it goes to the lexical environment of its Lexical parent. (Lexical Environment = Local Memory + Lexical Environment of Lexical Parent).
  • If it doesn’t find here also, then it goes to the lexical environment of its lexical parent’s parent.
  • This process continues until it finds the variable or until it finds null in the parent’s lexical environment. So whenever it sees that there is no more parent’s lexical environment to go, then it stops execution and throws an error like this Uncaught ReferenceError: <variable_name> is not defined

This chaining process is called scope chain also.

Similarly, in this step (step 6),

  • FEC 2 will search for i in its local memory but won’t find it.
  • Then it’ll go to the lexical environment of FEC 1 and search for i in the local memory but won’t find it.
  • Then it’ll go to GEC and search for i. Now it’ll find i and will print 10 to the console.

How JS Codes Get Executed -Explained by Showrin Barua

Step 7 (Destruction of FEC 2 Context)

As all the code statements inside FEC 2 context gets executed,

  • JS engine will destroy the context FEC 2
  • It will also pop out the context from the call stack
  • After that it’ll return the control to the context that is on the top of the call stack (i.e. FEC 1 or context of sqr)

How JS Codes Get Executed -Explained by Showrin Barua

Step 8 (Destruction of FEC 1 Context)

JS engine will execute return num * num.

  • Once the JS engine meets a return keyword it will destroy the context FEC 1.
  • It’ll also pop out the context of sqr from the call stack.
  • Then it’ll return the control to the context GEC with a value of 100 (10 * 10).

How JS Codes Get Executed -Explained by Showrin Barua

Step 9 (Assigning value to a)

As JS engine had the value 100 while it handed over the control to GEC and the immediate statement after that is an assignment, it will assign that value to a.

How JS Codes Get Executed -Explained by Showrin Barua

Step 10 (Destruction of GEC Context)

In this step, the JS engine has arrived at the end of the code snippet. Now it has nothing to execute.

  • It will destroy the global context (GEC).
  • It will pop out GEC from the call stack also.

Hence, our whole code execution is completed.

How JS Codes Get Executed -Explained by Showrin Barua

And at the end of everything, the call stack will be empty as there is no more execution context exists.

How JS Codes Get Executed -Explained by Showrin Barua

The Takeaway

Whenever the JS engine starts to compile code, it first creates a Global Execution Context. After that, it creates Function Execution Contexts when it meets a function call. All the contexts are pushed to the call stack after being created.

In the creation phase, the Execution context allocates memory to all the variables and functions inside it. Execution context also keep the track of the lexical environment of its lexical parent.

In the execution phase, it executes the code statements line by line. It finds variables inside its local memory first, and then its parent’s lexical environment and its parent’s parent lexical environment.

Finishing the execution phase of a context, the JS engine destroys it and pops it out from the call stack.

In this way, when all the code statements get executed, the JS engine destroys the Global Execution Context and pops it out from the call stack.

That’s the whole execution flow of JS codes. If you found this article helpful, feel free to share it with others. Thank you.

References

JavascriptJS EngineInside Compiler

Stay Tuned To Get Latests