Scopes and Closures in JavaScript: An In-depth Analysis of Context and Variable Scoping

before ES6, JavaScript had only Global and Function scopes which were defined by var keywords. ES6 introduced two important and new JavaScript keywords: let and const which helped developers in reducing the scoping-based bugs. These two keywords provided the block scope in JavaScript. JavaScript has three types of scopes:

  • Block Scope

  • Global Scope

  • Function Scope

Later on in this article, we will discuss Closures and lexical scoping as well.

Block Scope

Block scoping in JavaScript refers to the scope of variables being limited to the nearest enclosing block, such as within a function, a loop, or an if statement. It was introduced in the ECMAScript 6 (ES6) specification with the let and const keywords. Let and const provides the block scope in javascript, variables declared inside { } (curly brackets) block cannot be accessed from outside the block.

let scoping = () => {
    if(true){
        let block = 10;
        var global = 20;
    }
    // console.log(block) --> It will throw an error as "block" is block scoped variable, hence can't be accessed outside its scope 
    console.log(global)
}
scoping();

Global Scope

The global scope in JavaScript refers to the outermost scope, or the highest level of scope, where variables and functions are defined. Variables and functions declared in the global scope can be accessed from anywhere in the JavaScript code.

When a variable is defined outside of a function or block, it becomes a global variable that can be accessed and modified throughout your code.

let globalVariable = 'JavaScipt';

function greet() {
  console.log(globalVariable);
}

greet(); // Output: 'JavaScript'
console.log(globalVariable); // Output: 'JavaScript'

Global variables are accessible from everywhere in your JavaScript program, including functions and blocks. However, it is generally regarded as a good practice to make use of global variables as little as possible because they might cause naming conflicts, accidental updates, and trouble maintaining and debugging code.

It is crucial to note that the global scope of JavaScript may alter when used in different settings (such as web browsers or Node.js). The global scope is related to the window object in a web browser but with the global object in Node.js. In a web browser, the window object represents the global scope and acts as the top-level object for the browser's JavaScript environment. It provides various properties and methods that allow interaction with the browser window, such as manipulating the document, handling events, and managing timers.

On the other hand, in Node.js, the global object serves as the global scope for JavaScript code running in the Node.js environment. It provides functionality that is commonly needed across different modules, such as the console object for logging, the process object for accessing command-line arguments and environment variables, and various utility functions. Although both the window object and the global object represent the global scope in their respective environments, they have some differences in terms of the available properties and methods. The window object in a browser environment has additional properties and methods related to the browser's window and DOM manipulation, while the global object in Node.js provides additional functionality related to server-side JavaScript execution, file system operations, and networking.

It's important to be aware of these differences when writing code that targets specific environments, as certain features or APIs may be available in one environment but not in the other.

Function Scope

Function scope refers to the visibility and accessibility of variables and functions within a specific function in JavaScript. In JavaScript, each function creates its own scope, meaning that variables and functions defined inside a function are only accessible within that function unless explicitly returned or assigned to a higher scope.

function exampleFunction() {
  var x = 10; // x is defined within the function scope of exampleFunction

  console.log(x); // Output: 10
}

console.log(x); // Throws an error because x is not defined in this scope

The variable x is declared and given the value 10 inside the exampleFunction in the code above. This indicates that x is only used within the exampleFunction function scope. The second console.log sentence demonstrates how trying to access x outside of the function would result in an error because x is not defined in that scope.

It's crucial to keep in mind that JavaScript has block scope as of ES6 with the addition of the let and const keywords. Let and const variables are only available within the block they are defined in (such as an if statement or a loop), which is known as block scope.However, regardless of the block in which they are specified, variables declared with var continue to have function scope.

Closures

A closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function’s variables — a scope chain.

The closure has three scope chains:

  • it has access to its own scope — variables defined between its curly brackets

  • it has access to the outer function’s variables

  • it has access to the global variables

To the uninitiated, this definition might seem like just a whole lot of jargon! Let's understand closures through code.

function fruit() {
    let kingOfFruits = 'Mango';

    function sentence() {   
        let title = 'King'; 
        console.log(`${kingOfFruits} is the ${title} of all fruits`);
    }

    return sentence;
}
let value = fruit();
console.log(typeof(value); //legacy is of type function
value();

/*
    Output:
        function
        Mango is the King of all fruits
*/

Here we have two functions:

  • an outer function fruit which has a variable kingOfFruits, and returns the sentence function an inner function sentence which has its variable called title, and accesses a fruit variable kingOfFruits, within its function body The scope of variable kingOfFruits is limited to the fruit function, and the scope of variable title is limited to the sentence function.

  • On invoking the fruit() function, the result of the fruit() function is stored in variable value which is of type function.

So now let’s examine step-by-step what happens when value() function is invoked:

  • Variable title is created, and its value is set to King

  • JavaScript now tries to execute console.log('I have: ', kingOfFruits, title) — Here is where things get interesting. JavaScript knows that title exists since it just created it. However, variable kingOfFruits no longer exists. Since kingOfFruits is part of the fruit function, kingOfFruits would only exist while the fruit() function is in execution*. Since the fruit() function finished execution long before we invoked value(), any variables within the scope of the fruit function cease to exist, and hence variable kingOfFruits no longer exists.

This may seem as a weird example but its pure logical when gone through it carefully. Closures are one of those subtle concepts in JavaScript that are difficult to grasp at first. But once you understand them, you realize that things could not have been any other way.

Lexical Scoping

Lexical scope is a scope that is defined at the lexing time. In other words, the lexical scope is based on where variables and blocks of scope are authored, by you, at write-time, and thus is set in stone by the time the lexer processes your code.

It is also called a Static Scope. In a lexically scoped language, the scope of an identifier is fixed to some region in the source code containing the identifier’s declaration. This means that an identifier is only accessible within that region.

Lexical scoping, also known as static scoping, is a scope resolution mechanism used in JavaScript and many other programming languages. It determines the visibility and accessibility of variables and functions based on their position and lexical structure in the source code, rather than the runtime flow of the program.

In JavaScript, lexical scoping means that variables and functions are resolved based on their location in the source code during the parsing phase, rather than their location during runtime.