JavaScript Notes

Day 4

ยท

5 min read

Execution of JavaScript

JavaScript is classified as an interpreted language because of the way it is executed. An interpreted language is a type of programming language where the source code is executed line-by-line directly, without the need for a separate compilation step. When you run a JavaScript program, the code is not compiled into machine code beforehand. Instead, the code is executed directly by the JavaScript engine, which acts as an interpreter.

Modern web browsers and other JavaScript runtime environments have JavaScript engines that interpret the code in real-time. Due to this, JavaScript code can be easily executed on different platforms and devices. However, it also makes the code execution relatively slower compared to compiled languages, which have efficient machine code before execution.

In recent years, just-in-time (JIT) compilers have been introduced in JavaScript engines to improve performance, which translates frequently executed JavaScript code into machine code. Combining the two approaches, a balance between flexibility and speed is provided.

Execution Context

When the JavaScript engine executes the code, it creates an execution context. Execution context defines the environment in which code is executed. It consists of all the necessary information for the JavaScript engine to execute the code successfully.

There are three types of execution contexts in JavaScript:

  1. Global Execution Context: The global execution context is the outermost context and represents the code that is not inside any function. It is created when the JavaScript engine starts executing a script. Variables and functions declared in the global scope are added as properties and methods of the global object. this and globalThis objects are references to this global object.

    ๐Ÿ’ก
    In Browser, this refers to the window object.
    ๐Ÿ’ก
    In Node.js, this refers to the global object.
  2. Function Execution Context: Whenever a function is called, a new function execution context is created. Each function call has its own execution context. This context includes information about the function's arguments, local variables, and a reference to the outer (enclosing) execution context, which is the context of the function in which the current function was declared. This reference is used for variable scope resolution during the function's execution.

    ๐Ÿ’ก
    Here instead of the global object, the arguments object is created.
    ๐Ÿ’ก
    functions can use the arguments passed to it using the arguments keyword, which provides an array of all the arguments passed regardless of the parameters defined in the function declaration.
  3. Eval Execution Context (Not Recommend To Be Used): The eval() function in JavaScript can be used to execute code dynamically. When eval() is invoked, an eval execution context is created. However, using eval() is generally discouraged due to security and performance concerns.

To keep track of all the execution contexts, including the global execution context and function execution contexts, the JavaScript engine uses the call stack, which is described later.

Each execution context has three phases:

  1. Creation Phase: In this phase, first the memory heap is set up for storing variables and function references. And then, the function declarations are stored within the heap and the variables are initialized to undefined. In other words, the variables and function declarations are hoisted.

    ๐Ÿ’ก
    Note: Not all functions are hoisted. The code block below shows the different types of functions and whether they are hoisted or not.
  2. Execution Phase: In this phase, the JavaScript engine executes the code line by line, assigning the values and executing the functions. For each function execution, a new function execution context is created.

  3. Cleanup Phase: When the code in the execution context finishes executing, the execution context is removed from the top of the stack, and the control returns to the previous execution context. This process continues until all the execution contexts including the global execution context are removed from the stack and the script ends.

//function expression --> not hoisted
let arr = function() {
    return 10;
}

//arrow functions --> not hoisted
let score = val => val * 10;

//function declarations --> hoisted
function run() {
    return 20;
}

Call Stack

As mentioned before, JavaScript uses a call stack to keep track of all the execution contexts it has created and to keep track of its place in the code that calls multiple functions.

๐Ÿ’ก
A call stack is a data structure which works based on the Last-In-First-Out (LIFO) principle.

At the start of the script, the JavaScript engine creates the global execution context and pushes this context into the call stack. Whenever a function is called in the script, a new function execution context is created by the JavaScript engine which is then pushed on the top of the call stack. When the current function completes execution, the JavaScript engine pops the current function execution context from the call stack and resumes the execution in the current function where it left off. The script execution is completed once the call stack becomes empty, i.e., the global execution context is also removed from the call stack.

๐Ÿ’ก
The global execution context will always be at the bottom of the stack. All the function execution contexts will be pushed and popped from above.

The call stack has a fixed size based on the host environment. If the number of execution contexts exceeds the size of the stack, a stack overflow error occurs.

๐Ÿ’ก
A recursive function places one function execution context above the other and we run into stack overflow if there is no exit condition.
ย