Deep Dive into JavaScript: Lexical Scope, Closures and "this" keyword
Tags: webdev, javascript, programming, learning
Published: 2024-03-24 16:15:12

## Introduction
JavaScript, widely known as the programming language of the Web, proves to be a fundamental tool for creating interactive user interfaces and powering backend functionalities. As we delve deeper into the world of JavaScript, we come across intriguing concepts like lexical scope, closures, and the frequently misunderstood `this` keyword. In this article, I aim to shed light on these three concepts and demonstrate how lexical scoping intertwines with closures and the dynamic resolution of the `this` keyword.

## Understanding Lexical Scope
When we talk about lexical scope in JavaScript, we're referring to how variables and functions become accessible based on their placement within the code's physical structure, such as nested blocks or functions. This concept is fundamental, since **JavaScript uses lexical (static) scoping to resolve variable names at write time.**


**Lexical scope:**

- Cares about where variables and functions are defined.
- Considers nesting of blocks or functions. 
- Determines scope based on where variables and functions are defined within this nested structure, not just where the code physically exists in the file.

**This leads to the following lexical scoping rules:**
- Inner functions or blocks can access variables declared in their outer function or block.
- Inner scopes can reassign variables declared with `let` or `var` from outer scopes within the same scope or inner scopes.
- Inner scopes cannot directly modify `const` variables declared in outer scopes by reassigning the entire variable reference. However, they can modify properties of objects and arrays declared with `const` if those properties are mutable.
- When a variable is referenced, JavaScript looks for variables or functions starting from the current scope and then progressively through outer scopes until reaching the global scope, allowing for a **hierarchical resolution of identifiers**.

Let's consider the following example:

```javascript
function greet() {
  const message = 'Hello';

  function sayHello(name) {
    console.log(`${message}, ${name}!`);
  }

  sayHello('John'); // output: Hello John!
}

greet();
```
In this example, `message` is defined within the `greet` function's scope, allowing the `sayHello` function to access it due to lexical scope rules. When `greet` is called, it invokes `sayHello` with the argument '**John**', resulting in the output "**Hello, John!**".

Now, how about the next one?

```javascript
function greet() {
  let greeting = 'Hello'; 

  const person = { name: 'Alice' }; 

  function updateGreeting(newGreeting) {
    greeting = newGreeting; 
    console.log(greeting); // Output: Updated greeting
  }

  function updateName(newName) {
    person.name = newName; // Modifying a property of an object declared with const
    console.log(person.name); // Output: Bob
  }

function updatePerson(newName) {
    // Attempting to reassign the entire object reference for a const variable (will result in an error)
    person = { name: newName }; // Error: Assignment to constant variable
  }

  updateGreeting('Updated greeting');
  updateName('Bob');
  updatePerson('Jane');
}

greet();

```

Let's break down the behavior of the three inner functions:
- `updateGreeting`: This function takes a new greeting as an argument and updates the `greeting` variable declared with `let`. Since `let` variables are mutable, we can reassign their values within the same scope or inner scopes. Therefore, we get the output as the updated value "**Updated greeting**".
- `updateName`: This function takes a new name as an argument and modifies the `name` property of the `person` object declared with `const`. The `const` keyword allows the modification of properties if the properties themselves are mutable. Hence, this function successfully updates and logs the new name "**Bob**".
- `updatePerson`: This function attempts to reassign the entire object reference of the `person` object to a new object with a different name. However, since `person` is declared with `const`, it cannot have its reference reassigned after initialization. This attempt to reassign the object reference leads to an error `(TypeError: Assignment to constant variable)`.

Now that we have a solid understanding of lexical scoping rules, let's delve into one of the most powerful concepts that stems from it: **closures**. 


## Exploring Closures
Closures in JavaScript are like a dynamic duo formed by a function and the environment it's created in. They work hand in hand with lexical scoping, allowing a **function to retain accessibility to its surrounding variables even after the outer function has finished executing**.

Take a look at the following snippet:

```javascript
function outerFunction() {
  const message = 'Hello';

  function innerFunction() {
    console.log(message); // Accessing message from outerFunction
  }

  return innerFunction;
}

const myClosure = outerFunction();
myClosure(); // Output: Hello
```
In this example, `innerFunction` forms a closure with the `message` variable defined in `outerFunction`. Even after `outerFunction` completes its execution, `myClosure` retains access to `message` due to the closure mechanism, leading to the output "**Hello**".

**It's important to note that closure formation occurs during the definition of the inner function, and not during runtime.**

Now that we've explored how closures work within the context of lexical scope, let's shift our focus to another crucial aspect: the resolution of the `this` keyword.


## Decoding `this` Keyword
In JavaScript, the `this` keyword is a special identifier that **refers to the current execution context** within which a function is invoked. It plays a crucial role in determining the context for accessing properties and methods.

The resolution of `this` varies depending on the type of function being used, particularly between regular functions and arrow functions.

Consider the following example:

```javascript
const obj = {
  name: 'Alice',
  greet: function() {
    return function() {
      return this.name;
    };
  },
  arrowGreet: function() {
    return () => this.name;
  }
};

const regularGreet = obj.greet(); // Returns a regular function
const arrowGreet = obj.arrowGreet(); // Returns an arrow function

console.log(regularGreet()); // Output: undefined
console.log(arrowGreet()); // Output: Alice
```

The `greet` method returns a regular function that tries to access `this.name`, but because it's a regular function, `this` inside it refers to the **global scope** (or **undefined** in strict mode), resulting in the output being **undefined**.

On the other hand, the `arrowGreet` method returns an arrow function. Arrow functions do not have their own `this` context; instead, they inherit `this` from the surrounding lexical scope, which in this case is the `obj` object. Therefore, `this.name` inside the arrow function correctly refers to the `name` property of the `obj` object, resulting in the output being "**Alice**".

Hence, we can observe that:
- **Arrow functions maintain the lexical scope of `this`.**
- **Regular functions have their own `this` context that depends on how they are invoked.**

## Conclusion:
In this journey through JavaScript's foundational concepts, we've explored the workings of lexical scope, closures, and the dynamic resolution of the `this` keyword.

Lexical scope, with its emphasis on variable and function accessibility based on code structure, provides a structured approach to managing scope and defining function behavior.

Closures, as dynamic pairs between functions and their environments, enable functions to retain access to variables even after their outer function execution completes, adding a layer of flexibility and persistence to our code.

The resolution of the `this` keyword clarifies the context within which functions operate. 

By mastering these concepts, JavaScript developers can gain a deeper understanding of the language's inner workings, enabling them to write more efficient and robust code. I hope that this journey through JavaScript's foundational concepts has provided valuable insights for developers. Happy coding!