# Part 1: Execution Context - The core of Javascript

# Introduction

JavaScript’s **Execution Context** is foundational yet complex concept for understanding how JavaScript runs your code. If you aim to write performant, error-free code and debug complex applications, mastering Execution Context is crucial.

In this guide, we’ll unpack the key components of Execution Context, its lifecycle, how it interacts with the call stack, and explore some nuances such as hoisting, closures, the mysterious this binding, and the scope chain.

# 1\. What is Execution Context

At its core, an **Execution Context (EC)** is an environment where JavaScript code is evaluated and executed. Think of it as the space JavaScript creates to handle the code execution, where it manages variables, functions, and the context of `this`. JavaScript, as a single-threaded language, uses this context to manage the order and scope of code execution.

## Types of Execution Contexts

There are three primary types of Execution Contexts in JavaScript:

1. **Global Execution Context (GEC):** Created by default when JavaScript starts. This context is at the top of the scope chain and has two main functions:
    
    * It sets up the global `window` object (or `global` in Node.js).
        
    * It assigns a value to `this` in the global scope.
        
    
    Some points to remember about GECs -
    
    * There's only one GEC per program
        
    * Forms the base execution context
        
2. **Function Execution Context (FEC):** Created whenever a function is called. Each function has its own execution context, which is destroyed when the function completes execution. Each FEC has access to:
    
    * The function’s arguments and variables.
        
    * An optional `this` binding (set when called as an object method).
        
    * A reference to its outer environment, forming a scope chain.
        
    
    Some points to remember about -
    
    * Each function call creates a new execution context
        
    * Multiple FECs can exist simultaneously
        
    * Managed via the call stack
        
3. **Eval Execution Context:** Created when JavaScript’s `eval()` function is used, allowing execution of arbitrary code within a function or global scope. However, `eval` is generally discouraged due to security and performance issues.
    

## The Lifecycle of Execution Contexts

The lifecycle of an Execution Context includes three phases:

1. **Creation Phase (Memory Creation):**
    
    * JavaScript initializes the Execution Context and its environment.
        
    * During this phase, the **Variable Object (VO)** or **Lexical Environment** is created, hoisting variables and functions.
        
    * `this` binding and the scope chain are set.
        
        ```typescript
        console.log(myVar); // undefined
        console.log(myFunc); // [Function: myFunc]
        
        var myVar = "Hello";
        function myFunc() {}
        
        /** 
        During the creation phase, the following happens - 
        1. Variable Environment Creation
           1.1 Creates memory for variables and functions
           1.2 Variables initialized with undefined
           1.3 Functions stored in their entirety
        2. Scope Chain Establishment
           2.1 Creates scope chain for variable lookup
           2.2 Links to outer environments
        3. this Binding
           3.1 Determines value of this
           3.1 Affected by call site and function type
        */
        ```
        
2. **Execution Phase:**
    
    * JavaScript begins executing code, with variables and functions now fully accessible within their respective scopes.
        
        ```typescript
        let x = 10;
        function foo() {
            let y = 20;
            function bar() {
                let z = 30;
                console.log(x + y + z);
            }
            bar();
        }
        foo();
        
        /** 
        During the creation phase, the following happens - 
        1. Code is executed line by line
        2. Variable assignments performed
        3. Function calls trigger new execution contexts
        */
        ```
        
3. **Destruction Phase:**
    
    * After executing, the context is popped off the call stack, freeing up memory.
        

# 2\. Call Stack and Execution Context

JavaScript’s **Call Stack** is a data structure used to manage function calls and track Execution Contexts. When a function is called, its Execution Context is created and added to the top of the Call Stack. As functions complete, they are removed from the stack.

For example:

```typescript
function first() {
  console.log('First function');
  second();
}
function second() {
  console.log('Second function');
  third();
}
function third() {
  console.log('Third function');
}

first();
```

## Breakdown of Execution Context Creation on the Call Stack:

1. The **Global Execution Context** is created first and pushed onto the stack.
    
2. The `first()` function is called, so a new **Function Execution Context** for `first()` is created and added to the stack.
    
3. The `second()` function is called inside `first()`, so `second()`'s Execution Context is created and added to the top of the stack.
    
4. Similarly, `third()` is called, creating `third()`'s Execution Context on top of the stack.
    
5. As `third()` completes, its Execution Context is removed, followed by `second()` and then `first()`, until only the Global Execution Context remains.
    

## Call stack management also performs the following

* Maintains execution context order
    
* LIFO (Last In, First Out) structure
    
* Stack frame per function call
    
* Maximum call stack size limits recursion
    

# 3\. Creation Phase: Variable Environment, Scope Chain, and `this` Binding

## Variable Environment and Hoisting

During the creation phase, JavaScript performs **hoisting**, moving all variable and function declarations to the top of the scope. JavaScript essentially creates placeholders for these variables before code execution.

* **Function Declarations** are fully hoisted, allowing functions to be called before their declaration.
    
* **Variable Declarations** with `var` are hoisted but initialized with `undefined`.
    
* Variables declared with `let` and `const` are also hoisted but remain uninitialized until their definition, placing them in a **temporal dead zone (TDZ)**.
    

```typescript
var x = 1;
let y = 2;

function varVsLet() {
    var x = 10;  // Function-scoped
    let y = 20;  // Block-scoped
    
    if (true) {
        var x = 100; // Same variable
        let y = 200; // New variable
        console.log(x, y); // 100, 200
    }
    
    console.log(x, y); // 100, 20
}

// Variable Environment stores var declarations
// Lexical Environment stores let/const declarations
```

## Scope Chain

Each Execution Context has access to a **Scope Chain**, linking it to the parent execution context. This hierarchy is essential for **closures** and determines how variables are accessed in nested scopes.

Consider the example:

```typescript
function outer() {
  let a = 10;
  function inner() {
    console.log(a);
  }
  inner();
}
outer();
```

Here, `inner()` has access to `a` due to the scope chain, which connects `inner()` to `outer()`’s lexical scope.

## Block Scoping with let and const

```typescript
function blockScopeDemo() {
    let x = 1;
    
    if (true) {
        let x = 2;  // Different variable
        const y = 3; // Block-scoped
        console.log(x, y); // 2, 3
    }
    
    console.log(x); // 1
    // console.log(y); // ReferenceError
}
```

## `this` Binding

The `this` keyword behaves differently in various Execution Contexts:

* In the **Global Execution Context**, `this` refers to the global object (`window` in the browser).
    
* In a **Function Execution Context**, `this` refers to the object calling the function.
    
* Arrow functions inherit `this` from the outer scope due to lexical scoping.
    

For example:

```typescript
const obj = {
  name: 'JavaScript',
  regularFunction: function() {
    console.log(this.name);
  },
  arrowFunction: () => {
    console.log(this.name);
  }
};

obj.regularFunction(); // 'JavaScript'
obj.arrowFunction(); // undefined (inherits from global scope)
```

# 4\. Execution Context and Closures

A **closure** is formed when an inner function retains access to the lexical scope of an outer function, even after the outer function has completed. This retained scope allows inner functions to access variables in their parent scopes.

Example:

```typescript
function createCounter() {
  let count = 0;
  return function() {
    return ++count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
```

Here, `counter` retains access to `count`, forming a closure.

# 5\. Practical Insights: Hoisting, TDZ, and `let` vs. `var`

## Hoisting in Depth

Understanding hoisting can prevent unexpected behaviors in JavaScript. For example:

```typescript
console.log(x); // undefined
var x = 5;

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
```

Using `let` and `const` offers better control and avoids issues from hoisting and the temporal dead zone (TDZ), which prevents access before variable initialization.

# 6\. Context Loss and Preservation

```typescript
class MyClass {
    constructor() {
        this.value = 42;
    }
    
    regularMethod() {
        console.log(this.value);
    }
    
    arrowMethod = () => {
        console.log(this.value);
    }
}

const instance = new MyClass();
const regular = instance.regularMethod;
const arrow = instance.arrowMethod;

regular(); // undefined (context lost)
arrow();   // 42 (context preserved)
```

# 7\. Context Loss and Preservation

```typescript
class AsyncHandler {
    constructor() {
        this.data = 'Initial';
    }
    
    async regularAsync() {
        setTimeout(function() {
            console.log(this.data); // undefined
        }, 100);
    }
    
    async arrowAsync() {
        setTimeout(() => {
            console.log(this.data); // 'Initial'
        }, 100);
    }
}
```

# 8\. **this Binding and Context**

## **8.1 Default Binding**

```typescript
function showThis() {
    console.log(this);
}

// In non-strict mode
showThis(); // window/global

// In strict mode
'use strict';
showThis(); // undefined
```

## **8.2 Implicit Binding**

```typescript
const obj = {
    name: 'Object',
    showThis() {
        console.log(this.name);
    }
};

obj.showThis(); // 'Object'
```

## **8.3 Explicit Binding**

```typescript
function display() {
    console.log(this.name);
}

const person = { name: 'John' };

display.call(person);   // John
display.apply(person);  // John
const bound = display.bind(person);
bound();               // John
```

# **Conclusion**

Understanding JavaScript's Execution Context is crucial for writing robust and maintainable code. It affects everything from variable scope to this binding and is fundamental to features like closures and the module pattern. Mastering these concepts allows developers to write more predictable and efficient JavaScript code.

The complexity of execution contexts demonstrates why JavaScript is both powerful and sometimes counterintuitive. By understanding these mechanisms, developers can better anticipate behavior and avoid common pitfalls in their applications.
