Demystifying JavaScript Execution: A Layman's Guide

Demystifying JavaScript Execution: A Layman's Guide

Have you ever wondered what exactly happens when you hit that "Run" button in your JavaScript program?

Let's take a journey through the intricacies of JavaScript execution and uncover the magic that takes place behind the scenes.

The Script: A Story in Code

function calculateSum(a, b) {
    let result = a + b;

    function multiplyByTwo(num) {
        return num * 2;
    }

    let doubleResult = multiplyByTwo(result);

    return doubleResult;
}

let finalResult = calculateSum(3, 5);

Act 1: Setting the Stage - Global Execution Context

When we run the above code a global execution context is created.

The execution context is created in two phases.

  • 1st phase is the memory creation phase.

  • 2nd phase is the code execution phase.

Memory Creation Phase

With the above code, in the first phase, javascript will allocate memory to all the variables and functions.

  • The calculateSum function takes its place in the memory spotlight. In case of functions, it will store the entire function code in the memory space.

  • finalResult is allocated memory with the value undefined in the first phase.

  • In the bottom of the stack we have our global execution context that means, whenever any JS program is run, this call stack is populated with this Global execution context. This whole execution context is pushed inside this stack.

Code Execution Phase

Now javascript once again runs through the whole javascript code line by line.

  • From line 1 to 11, there is nothing to execute

  • The calculateSum function steps into the spotlight. Functions are like mini programs. When a function in invoked a new execution context gets created.

    • On line 13, a new execution context gets created with two components. Now, again we will go through two phases which are memory allocation and code execution.

    • This execution context is only restricted with function code which is from line 1 to 11.

    • Whenever a function is invoked, or a new execution context is created, that new execution context is put inside the stack which is EC1 here.

Act 2: Drama within Functions

Memory Creation Phase (calculateSum Execution Context)

Now we dive into a subplot:

  • Memory allocates space for a, b, result and doubleResult as undefined.

  • The inner function multiplyByTwo is also added in the memory with the entire function code.

Code Execution Phase (calculateSum Execution Context)

The plot thickens:

  • result receives its value as 8.

  • The inner function multiplyByTwo is encountered.

Intermission: A Quick Side Story

Memory Creation Phase (multiplyByTwo Execution Context)

  • Memory allocates for the parameter num as undefined .

  • As a new function was invoked, a new execution context gets created i.e. EC2.

Code Execution Phase (multiplyByTwo Execution Context)

  • The parameter num gets its value.

  • The multiplication performance begins.

    • the return num *2 tells the function that it is done with the work and just return the control back to the local execution context of calculateSum() where the function was invoked.

    • When it will encounter the return num * 2 statement, it finds the value of num in the local memory, performs the calculation and the control goes to the execution context of calculateSum() where it will replace undefined of doubleResult with 16.

    • Once the whole function code is executed, the execution context for the instance of that function will be deleted.

    • Then, EC2 is popped out of the stack, and the control goes back to the execution context of EC1.

Back to the Previous Act: Resuming calculateSum

The stories intertwine:

  • Next, the statement return doubleResult will again, return the control back to the global execution context where the function was invoked.

  • The finalResult will now the get its value from doubleResult.

  • Once the whole function was executed, then EC1 will also move out of stack, and the control goes back to Global Execution Context (GEC).

The Finale

  • Now, as the entire code is done with its executions, the entire global executed context is also deleted.

  • As this whole program is executed, the call stack gets empty. The GEC is also popped out from this call stack and we are done with our JavaScript program. So that is how the whole code inside the JS Engine is executed.

The entire flow:

The Harmony of the Call Stack

JavaScript maintains a call stack to manage the order of execution contexts.

  • The global execution context starts at the bottom of the stack.

  • Function invocations push new execution contexts onto the stack.

  • After execution, contexts pop off the stack.

This dance continues until the program concludes, leaving an empty call stack.

Let's take an example to understand:

function outer() {
  var outerVar = "I am outer!";

  function inner() {
    var innerVar = "I am inner!";
    console.log(outerVar); // Output: I am outer!
  }

  inner();
  console.log(innerVar); // Error: innerVar is not defined
}

outer();

Code Flow in terms of Execution Context and Callstack:

  1. Global Execution Context (GEC):

    • Memory Phase:

      • outer: <function code>

Call Stack: [GEC]

  1. Execution of Function outer():

    • Local Execution Context for outer:

      • Memory Phase:

        • outerVar: undefined

Call Stack: [GEC, outer()]

  1. Execution of Function outer() (Continued):

    • Memory Phase (Contd.):

      • outerVar: "I am outer!"
    • Execution Phase:

      • Call to inner() pushes a new local Execution Context for inner onto the Call Stack.

Call Stack: [GEC, outer(), inner()]

  1. Execution of Function inner():

    • Local Execution Context for inner:

      • Memory Phase:

        • innerVar: undefined
    • Execution Phase:

      • Memory for outerVar is accessible within the scope of inner.

      • console.log(outerVar) prints I am outer!.

    • Removal of inner() from Call Stack.

Call Stack: [GEC, outer()]

  1. Back to outer() Execution:

    • Execution continues after the call to inner().

    • Attempting to print innerVar in the console results in an Error since innerVar is not accessible outside its scope.

    • Removal of outer() from Call Stack.

Call Stack: [GEC]

  1. Program Conclusion:

    • GEC is deleted.

    • Call Stack is empty.

Key Takeaways

  1. JavaScript execution starts with the creation of a global execution context.

  2. Execution contexts consist of a Memory Component and a Code Component.

  3. The Memory Creation and Code Execution phases manage variable and function allocation.

  4. Function invocations create new execution contexts.

  5. The call stack determines the sequence of execution contexts.

Now, the next time you run your JavaScript code, visualize the dance of execution contexts and the orderly steps taken by the call stack.

Happy coding!

Did you find this article valuable?

Support Prerana Nawar by becoming a sponsor. Any amount is appreciated!