JavaScript Execution Context demystified

JavaScript Execution Context demystified

The last article you'll ever need to understand this concept.

When people use language, their words exist in a specific environment, a unique context that plays a role in shaping their meaning. Factors like social norms and cultural beliefs impact how speakers and writers use language and how those around them interpret it. Variables such as where we use certain words and how we communicate them also shape our understanding. The context people use in language is thus critical to understanding the meaning of speech and writing.

- StudySmarter

For me, the first thing to do when trying to understand a concept is to pay attention to the words used to form its name or describe it. Most of the time, everything starts making sense when I begin with this approach.

Let's attempt to understand what an Execution Context is by examining both words used to form the term.

Execution is the process of carrying out an action/plan.

Context is a frame that surrounds an event and provides resources for its appropriate interpretation - Wikipedia

Just as context helps us correctly understand a statement in the English language, in the JavaScript language an execution context is simply the environment in which our code is executed. This environment helps us understand how the code we write is executed.

This means that the plans or actions (code) written in a javascript program can be fully understood within a context i.e., according to the surrounding in which it (the code) is executed.

In Javascript, an execution context is created when any of the following occurs;

  1. When you load a script file e.g index.js

  2. When a function is called.

The execution context created when the script file is loaded is called the Global Execution Context, while the context created when a function is invoked is called a Function Execution context.

Every JavaScript program has just one Global Execution context but can have more than one function execution context. Only one context is allowed to execute per time.

Let's quickly learn about what happens in an execution context.
The interpretation of our JavaScript code within a context - whether global or function execution context - happens in two phases;

  • Creation Phase: The execution context is created, and all variable and function declarations in the context are memorized.

  • Execution Phase: The codes in the created context are executed.

Let's consider what happens in both phases of the global context and then the function context.

The Global Execution Context (GEC)

All code written outside a function will run in the global execution context and as we have mentioned earlier, the code in the snippet below will be interpreted in two phases;

var city = "Lagos";

function printLocation (prefix) {
    var country = "Nigeria";

    console.log(`${prefix} ${city}, ${country}`);
}

printLocation("User is located at")

Creation Phase (GEC)

During the creation phase of the Global Execution Context;

  1. The global object is created. In a web browser, the global object is called window in a web browser but in node.js it is called global.

  2. The keyword this is created and assigned to the global object.

  3. The parser scans through the entire code in that context and takes note of them. It's safe to say that the program memories them or puts them in memory (this is when hoisting occurs).

At this stage, all variables are stored in memory and given an initial value of undefined, while the initial value of a function declaration is itself. Let me further break this down.

At the creation phase, the parser takes note of all (function and variable) declarations in the currently executing context and saves them in memory with an initial value.

It is important to note that when you declare a variable like this var city = "Lagos", you are doing two things at once. The interpreter will break that down as two separate operations;

// 1. Variable declaration
var city; // At this point, the value of city is 'undefined'

// 2. Variable assignment
city = "Lagos" // At this point, the value of city becomes "Lagos".

What happens in the creation phase is that only declarations are memorised.

It is important to state that a function is a value. A function declaration is treated as a whole. This means it is not a statement that can be further broken down into two parts (name and value - like the case of variables).
This is why during the creation phase, a function is stored wholly in memory and a reference to it is generated which can be accessed using the function name.

For example; The function called printLocation captured in the code snippet above like this;

function printLocation (prefix) {
    var country = "Nigeria";

    console.log(`${prefix} ${city}, ${country}`);
}

Will be stored in memory during the creation phase just as it is. Its value in memory can then be referenced using its name.

NB: At the creation phase, the value of a variable declaration is always undefined while the value of a function declaration is the function itself.

Execution Phase (GEC)

During the execution phase, all other actions are executed i.e., any statement that is not a declaration e.g., variable assignments, function calls, loops, if...statements etc.

Still working with the code snipped above, let's see what happens in its execution phase.

  1. The value of the variable city gets updated to "Lagos".

  2. The function printLocation gets invoked

to further understand how things happen in the global execution context let's run through the flow from top to bottom. We'll assume that the code snippet below is a single javascript file index.js loaded onto a browser.

var city = "Lagos";

function printLocation (prefix) {
    var country = "Nigeria";
    console.log(`${prefix} ${city}, ${country}`);
}

printLocation("User is located at")

The code above is going to be compiled as follows;

  1. The compiler receives the program contained in the file index.js (represented in the code snippet above).

  2. A global execution context is created.

  3. The creation phase of the context kicks off and in this phase;
    - The global object is created and assigned to window.
    - The this keyword is generated and it points to window.
    - An object/memory space gets created for all the declarations in the context.
    - All variable and function declarations get stored in memory.
    This means at this stage, the variable city above will have its value set to undefined, while the function printLocation will be stored in the created memory as it is.

  4. The Execution phase begins.
    - The assignment part of the statement in line 1 of the code above gets executed. The result of this is that the value of city in memory will be updated to Lagos then the control flow moves to line 9 (since no execution happens between lines 2 and 8) remember that declarations have been taken care of in the creation phase.
    - The function call on line 9 gets executed, causing control flow to go into the printLocation function body thereby pausing the execution of the global context and creating a new context for the printLocation function to execute.

NB: Only one context can execute its code per time and when a function is invoked e.g printLocation("User is located at"), a function execution context is created and executed.

Let's now examine what happens in a function execution context.

Function Execution Context (FEC)

Whenever the compiler reaches a function call e.g printLocation("User is located at"), a new function execution context is created. This is the environment where all variables and functions passed into the function as an argument, declared or invoked within that function's body will operate.

Arguments are the comma-separated values provided within the opening and closing () brackets during a function call. For example, In this code printLocation("User is located at"), the string "User is located at" within the brackets used to invoke the printLocation function is an argument.

The function body is everything within the opening curly brace { and the closing curly brace } of the function declaration.

The function execution context also goes through the creation and execution phases to correctly interpret the instructions (code) existing in it.

Let's quickly consider what happens in the function execution context phase by phase.

Creation Phase (FEC)

During the creation phase of the function execution context, the following happens;

  1. An arguments object which holds all arguments passed into the function during its invocation is created.

  2. The this keyword is created and defaults to the global object.

  3. An object/memory is generated to store all declarations that exist within the function context (either passed into it via arguments or declared within the function body).

    All variables declared within the function body are given a default value of undefined while function declarations are stored as values in memory (this value can be referenced using the function name) but at this phase***, arguments passed into the function will get their actual value.***

Execution Phase (FEC)

In the execution phase, all variable assignments, function calls, for...loops, if...statements etc., are executed.

For example, in the body of the printLocation function above, the console.log() statement will be executed.

console.log(`${prefix} ${city}, ${country}`);

Immediately after the function is done executing, it returns control flow back to the context from where it was created and the execution of that context continues until there's no more code to be executed then the program exits.

This leads us to the last concept we need to cover in this article.

The Execution Stack

In programming, a stack is a data structure that allows the storage of data using the first in, last out principle (FILO).

In simple terms, a stack is like a box or a container with one opening for entry and exit. That means the first item that gets into the container will be the last to come out.

Kindly see the image below;

During the execution of a JavaScript program, execution contexts are put in what we call an execution or call stack.

Considering the code below;

// 1. GEC is created and added to the call stack.

function fec1() {
    fec2() //3. Invokes fec2 and creates its context and adds to stack
    function fec2 () {}
}

fec1() //2. Invokes fec1, creates context and adds to call stack.

The first execution context to be created (always the Global Execution Context) is put into the execution stack, and if during the execution phase of that context, a function execution context is created, the execution of the global context is paused and the newly created function context is added to the call stack and so on.

When all the instructions contained in a context have been executed, it gets popped off / removed from the call stack and then the context from which it was created resumes execution.

This means, the last/latest entry in the call stack is what gets executed and once the execution of that context is complete, it is removed from the stack and the context from which it was created resumes execution until there are no more contexts in the stack.

It is important to note that the call stack has a fixed size depending on the configuration of the environment in which JavaScript runs (browser or node.js).
A stack overflow occurs when the number of contexts in the stack exceeds that size.

Although JavaScript can perform multiple tasks per time using the event loop, JavaScript is single-threaded and can only run one context at a time.

In my next article, I am going to write about how the event loop works in JavaScript.

Summary

There are two types of contexts in JavaScript; Global and Function Execution Context. The global execution context is created once a script is loaded into the JavaScript engine. A function execution context may or may not be created. It gets created when a function is invoked.

Both execution contexts get processed in two phases; The Creation and Execution phases.

In the creation phase of both contexts, the this keyword gets assigned to the global object window by default, all variable declarations are added to a space in memory and assigned a value of undefined, function declarations get added to memory as they are.

The difference between what happens in the creation phase of both contexts is that during the execution of the global context, the global object is created but in a function context, the arguments object is created and all arguments passed into the function during its invocation are stored in it. Also, all arguments passed into the function are given their actual value in the creation phase.

During the execution phase of both contexts, all other statements that are not variable or function declarations are executed.

The execution or call stack is the mechanism that takes care of what execution contexts get executed per time.