JavaScript Closures

In JavaScript, a closure allows an inner function to access variables from its containing (outer) function, even after the outer function has completed execution. To put it simply, a closure is formed when a function is declared inside another function.

Through the implementation of closures, a function retains the ability to access variables from its surrounding context, even after that context has been terminated. This feature is beneficial for achieving modularity, facilitating functional programming, enabling asynchronous processes, and additional use cases.

Example

Example

function greet(){

    let name = "Rohit";

    function displayName(){

        return 'Hi' + ' ' + name;

     }

     return displayName;

}

const g1 = greet();

console.log(g1);

console.log(g1());

Output:

Output

[Function: displayName]

Hi Rohit

Lexical Scoping

In JavaScript, closures are based on lexical scoping, which indicates that the scope of a function is determined by its creation context rather than its execution context. As a result, inner functions can reach and utilize variables from their enclosing outer function.

Example

Example

function outer() {

  const outerVar = 'Hello, Welcome to our tutorial';

  function inner() {

    console.log(outerVar); // Can access outerVar because of lexical scope

  }

  inner();

}

outer();

Output:

Output

Hello, Welcome to our tutorial

Private Variables

In JavaScript, leveraging closures allows for the encapsulation of function variables, ensuring that they remain inaccessible outside of their defining function. Private variables are employed when developing modules, serving to safeguard data from potential alterations or unauthorized access by other segments of the code.

Example

Example

function counter() {

// Private variable

    let num = 10;     

    return function () {

     // Access and modify the private variable

        num++;

        return num;

    };

}

const increment = counter();

console.log(increment());

console.log(increment());

console.log(increment());

Output:

Output

11

12

13

Closures and IIFE

An Immediately Invoked Function Expression, commonly referred to as IIFE, employs closures to encapsulate data within the function itself. By utilizing IIFE alongside closures, one can maintain the confidentiality of information, shielding it from external functions. This technique enables the creation of self-sufficient modules.

Example

Example

const demo = (function(){

    let num = 0;

   return{

        increment: function(){

            num++;

            console.log(num);

        },

        reset: function(){

            num = 0;

            console.log("Welcome to our tutorial");

        },

    };

})();

demo.increment();

demo.increment();

demo.reset();

Output:

Output

1

2

Welcome to our tutorial

Closure and setTimeout

In JavaScript, closures play a crucial role in asynchronous programming as they maintain access to their surrounding context even after the execution of a function has concluded. The use of setTimeout in conjunction with closures proves beneficial for managing timers and handling server requests, particularly in scenarios where certain functions may not execute right away.

Example

Example

function x(){

    var i = 1;

    setTimeout(function(){

        console.log(i);

    }, 2000);

}

x();

Output:

Closure with this keyword

In JavaScript, closures can occasionally lead to confusion regarding the usage of the this keyword since its value is determined by the context in which a function is invoked, rather than its definition. Consequently, within a closure, the this keyword may not point to the expected object based on the function's location in the code.

Example

Example

function Student(grade){

    this.grade = grade;

    this.sayGrade = function(){

        console.log(this.grade);

    };

    setTimeout(function(){

        console.log(this.name);

    }.bind(this),1000);

}

const G = new Student("A");

G.sayGrade();

Output:

Output

A

Undefined

Function Currying in JavaScript

In JavaScript, the concept of function currying is a method that takes several arguments and transforms them into a series of functions, where each function is designed to accept a single argument. The mechanism of JavaScript function currying relies on closures, as it is essential to access the arguments that were provided to the preceding functions for each successive function in the chain.

In straightforward terms, it facilitates partial application and assists in the creation of reusable, tailored functions.

Example

Example

function add(a){

    return function(b){

        return a + b;

    };

}

const addTwo = add(2);

console.log(addTwo(3));

Output:

Benefits of JavaScript Closure

There are several benefits of JavaScript Closure:

Encapsulation

In JavaScript, leveraging closures allows you to establish private variables and functions that are exclusively accessible from within those closures. This technique can also enhance the organization and modularity of your code.

State retention

In JavaScript, leveraging closures allows you to preserve the state of a function even after its execution has concluded. This capability is particularly useful for developing function factories or implementing various design patterns that depend on maintaining state throughout several function invocations.

Currying

In JavaScript, by employing closures, it is possible to develop curried functions. These functions allow you to invoke them with a subset of arguments initially, subsequently yielding a new function that takes in the remaining arguments. The technique of currying enhances the flexibility and reusability of your code.

Memorization

In JavaScript, closures can be utilized to apply the memorization strategy, enabling a function to retain and leverage its past outcomes. By employing this approach, you can enhance the efficiency of your code.

Asynchronous Programming

In JavaScript, closures provide a way to streamline asynchronous programming by enabling the definition and execution of callback functions or asynchronous logic within the scope of the asynchronous operation itself. This approach eliminates the need to define these functions separately and pass them as arguments.

Event handling

Closures are a powerful tool for crafting event handlers that retain access to variables and functions established in their outer scope. This capability proves to be extremely beneficial for developing intricate event-driven functionalities within your applications.

Limitations of JavaScript closures

JavaScript closures come with certain drawbacks. These include:

Memory Leaks

In JavaScript, if closures are not handled with caution, they can result in memory leaks. This occurs because closures maintain a reference to variables from their surrounding scope, even after the completion of the outer function, which prevents those variables from being eligible for garbage collection.

Performance overhead

The process of creating and managing closures may lead to a certain degree of performance overhead. Each closure establishes a distinct scope, which can contribute to the intricacy involved in the scope chain resolution.

Debugging Complexity

Debugging can become more complex when dealing with closures. When an error arises within a closure, identifying the origin of the issue can prove to be more challenging because of the presence of nested scopes and the retention of variables from surrounding scopes.

Increased code complexity

Excessive reliance on closures may result in code that is more intricate and difficult to comprehend and maintain. Although closures provide benefits such as encapsulation and data privacy, they must be employed with care to prevent introducing unwarranted complexity.

Input Required

This code uses input(). Please provide values below: