🚀How JavaScript Works (Part 2)? The lexical scope mechanism of JavaScript
Tags: webdev, javascript, programming, tutorial
Published: 2023-10-25 15:41:32

Understanding **lexical scope** is essential for mastering JavaScript, as it dictates how variables are accessed and resolved at compile time. This article explores the core principles of **lexical scope**, delving into the definition, how it shapes variable behavior, and its role in the JavaScript two-pass system. We'll also examine the creation of scopes through functions and blocks and explore key concepts like **shadowing**, **global variables**, **strict mode**, types of errors, **undeclared** vs. **undefined** variables, nested scopes, and the role of arguments and parameters in JavaScript functions.

## Table of Content
- [Lexical Scope Definition](#lexical-scope-definition)
- [JavaScript's Two-Pass System](#javascripts-two-pass-system)
- [Organizing Scopes with Functions and Blocks](#organizing-scopes-with-functions-and-blocks)
- [How JavaScript Creates Scopes](#how-javascript-creates-scopes)
- [Shadowing](#shadowing)
- [Global Variables and Strict Mode](#global-variables-and-strict-mode)
- [Types of Errors](#types-of-errors)
- [Undeclared vs. Undefined](#undeclared-vs-undefined)
- [Nested Scopes](#nested-scopes)
- [Arguments and Parameters in JavaScript](#arguments-and-parameters-in-javascript)
- [Conclusion](#conclusion)
- [Sources](#sources)

## Lexical Scope Definition

**Lexical scope** consists of two key components: **lexical** and **scope**. Lexical refers to the **lexing** phase of compilation, which we discussed in a [previous article](https://dev.to/samanabbasi/how-javascript-works-part-1-look-at-js-engine-1kkb). But what exactly is **scope**?

> **Scope is the region where JavaScript searches for and accesses variables.**

It answers two essential questions:

### Key Questions

Lexical scope helps answer two key questions:

- **What are we looking for?** Lexical scope is primarily about finding identifiers, such as variables or functions.

- **Where are we looking for them?**
 Lexical scope defines the location where identifiers are accessible in your code.

In essence, we're primarily searching for identifiers, such as variables and functions, within various scopes in your code. Variables play two roles:

- **Receiving the assignment of a value** Variables in this role act as `targets`, where values are assigned, like `x = 42` or `myFunc = function func(arguments) {}`.

- **Retrieving a value from the variable** Variables in this role serve as `sources`, where values are retrieved, like `console.log(y)` or function calls, such as `ask()`.

During the code's execution, JavaScript engine processes scopes, asking two fundamental questions:

- **What is the position of this variable within the scope?**
- **To which scope does it belong?**

## JavaScript's Two-Pass System <a name="javascripts-two-pass-system"></a>

Understanding how JavaScript handles **lexical scope** involves recognizing that JavaScript is a two-pass system:

1. **process the code first:** Javascript processing of **lexical scopes** and putting all of these identifiers, into their correct scope in the parsing stage where it goes through all of our code, produces this abstract syntax tree (`AST`) and produces a plan. This plan is executable code that is handed off to be executed by the other part of the JavaScript engine (Virtual Machine),

2. **execute it:** JavaScript virtual machine, which is embedded inside of the same JavaScript engine. It interprets all that plan and use all that information to execute the code, and one of the things that it interprets is, whenever it enters a scope it creates all the identifiers according to what the plan told it to do and locates memory for them

## Organizing Scopes with Functions and Blocks

In JavaScript, **lexical scope**s are created when the code encounters `functions` and `blocks` (curly braces `{}`).

- **Functions:**
 When you declare a `function`, it creates a new scope. Functions can be nested within other functions, forming a hierarchy of lexical scopes.

- **Blocks:**
 Blocks defined by curly braces `{}` in constructs like `if`, `for`, and `while` statements also introduce new lexical scopes.

## How JavaScript Creates Scopes

When JavaScript encounters variable or function declarations, it creates scopes and places those identifiers within the appropriate scope. Consider the following code:

```javascript
var teacher = 'kyle';

function otherClass() {
	var teacher = "saman";
  	console.log("Welcome!")
}

function ask() {
	var question = "why";
  	console.log(question)
}
```
In this code, you can observe how JavaScript creates scopes and assigns identifiers:

- Variable `teacher` is assigned in the global scope.
- Function `otherClass` creates a new scope, and a local variable `teacher` is assigned in that scope.
- The function `ask` also creates a scope, where the variable `question` is defined.

In lexically scoped languages like Javascript, all the **lexical scope**s and identifiers, are determined at `compile` time not in run time. It is used at run time but determined at compile time, this behaviour allows the engine to be much more effective in optimising because everything is known and it is fixed.

> After all scopes are determined during the parsing stage, JavaScript knows the scope of each identifier. This knowledge helps optimize execution since everything is pre-determined at compile time.The decision about scopes are author's time decisions

## Shadowing
**When you have multiple variables with the same name at different scopes, it is called shadowing**. This means the innermost variable with the same name takes precedence when accessing that identifier. For example, if you have a local variable named `x` inside a `function`, it shadows an outer variable `x`.


## Global Variables and Strict Mode

JavaScript provides a mechanism for **dynamic global variables** (auto globals) that are automatically created if you try to assign to a variable that has never been formally declared. This behaviour occurs when the variable is not declared in any accessible scope. However, auto globals are discouraged in favour of explicitly declared variables as they can lead to performance issues.

**Strict mode** in JavaScript enforces stricter error handling and helps catch potential issues early in development. It's not always enabled by default but is commonly used in modern development tools and transpilers. Inside `class` and `ES6 modules`, you don't need to explicitly declare `"use strict"`; `strict mode` is assumed.

## Types of Errors

JavaScript can throw various types of errors:

- **TypeError:**
 Occurs when you perform illegal operations with variables, such as attempting to execute `undefined` or `null`, access properties on `undefined` or `null`, or reassign a `const` variable. 

> `TypeError` happens at runtime.

- **SyntaxError:**
 Happens when there are syntax errors in your code, such as extra parentheses or curly braces, improper dots, or illegal operations with `let` or `const`, like redefining a variable multiple times. 

> `SyntaxError` occurs during the compile time.

- **ReferenceError:** This error arises when you try to access a variable that doesn't exist, and JavaScript can't find it. `ReferenceError` includes situations like accessing undeclared variables or trying to access variables before they are declared. 

> `ReferenceError` happens at runtime.

## Undeclared vs. Undefined

Understanding the difference between `undeclared` and `undefined` is crucial in JavaScript:

- **Undefined:** It signifies that a variable exists but currently has no assigned value. It might have never had a value or previously had a value but doesn't at the moment. It represents an empty state, where the value is missing.

- **Undeclared:** An undeclared variable doesn't exist in any accessible scope. It's essentially unknown in your code, and trying to access it results in a `ReferenceError`.

## Nested Scopes
JavaScript's lexical scopes can be envisioned as nested layers, similar to a multi-story building. JavaScript conducts a sequential search for variables, beginning from the current scope (the first floor) and progressively moving to outer scopes. This process mimics a linear search, akin to using an elevator to traverse floors within a building.

In this conceptual analogy:

- The first floor represents the current scope, where a reference is made.
- The top floor corresponds to the global scope.
The search progresses one floor at a time, ensuring that variables are resolved in a structured and predictable manner. This methodology forms the foundation of JavaScript's lexical scope.

## Arguments and Parameters in JavaScript

In JavaScript, functions play a crucial role in processing data and performing tasks. To understand how functions work, it's essential to grasp the concept of arguments and parameters, which are integral to how functions receive and process data.

### Parameters: Receiving Data

Parameters are the placeholders or variables defined in a function's declaration. They act as targets for the data you pass to the function when you call it. Parameters specify what kind of data the function expects to work with and provide names for accessing that data within the function's scope.

Here's an example of a function with parameters:

```javascript
function greet(name) {
  console.log(`Hello, ${name}!`);
}
```
In this function, name is a parameter. It serves as a target for the data you pass when calling the greet function. For instance, if you call greet("Alice"), the parameter name will take on the value "Alice," and the function will greet Alice.

### Arguments: Passing Data

Arguments are the actual data or values you provide when calling a function. These values are assigned to the function's parameters. Arguments are the source of data that the function works with.

Here's how you pass arguments to the greet function:

```javascript
greet("Bob"); // "Bob" is the argument
greet("Carol"); // "Carol" is the argument
```
In these examples, "Bob" and "Carol" are the arguments that get assigned to the name parameter within the greet function.

### Parameters Create Identifiers

It's important to note that when you declare parameters in a function, they formally create identifiers within the function's scope. These identifiers allow the function to access and work with the data you pass as arguments.

In the greet function example:

```javascript
function greet(name) {
  console.log(`Hello, ${name}!`);
}
```
The name parameter is a formal identifier within the function's scope, allowing you to use it to reference the argument you pass when calling the function.

## Conclusion
By delving into these fundamental aspects of lexical scope, you'll gain a deeper understanding of JavaScript and how it manages variables, errors, and scope. This knowledge is invaluable for writing efficient, optimized code and solving complex programming challenges.

## Sources
**Kyle Simpson's "You Don't Know JS"**
**MDN** Web Docs - The Mozilla Developer Network (MDN)