Saturday, 16 November 2024

Closure in JavaScript

In JavaScript, closure is a powerful concept that allows a function to "remember" and access variables from its lexical scope, even when the function is executed outside that scope. To break it down:

Key Points of Closures:

  1. Lexical Scope: The scope in which a function is defined. JavaScript functions have access to variables in their lexical (or containing) scope.
  2. Inner Functions and Enclosing Variables: Closures are typically created when an inner function has access to variables from an outer function, even after the outer function has finished executing.
  3. Persistent Data: Closures allow inner functions to "remember" and retain access to variables from the outer function, making them useful for data encapsulation and privacy.

How Closures Work

When an outer function returns an inner function, the inner function still retains access to the variables from the outer function, even after the outer function has finished executing. This happens because the inner function "captures" the environment it was created in.

Example of a Closure:

    function outerFunction() {

          let outerVariable = 'I am from outer function';

          function innerFunction() {

            console.log(outerVariable); // inner function has access to outerVariable

          }

          return innerFunction; // Return the inner function as a closure

    }

     const closureFunction = outerFunction(); // outerFunction() executes, returning innerFunction

    closureFunction(); // Logs: "I am from outer function"

Explanation:

  • The outerFunction() defines a variable outerVariable and an inner function innerFunction().

  • When outerFunction() is called, it returns innerFunction(), which is stored in the variable closureFunction.

  • Even though outerFunction() has finished executing, closureFunction() still has access to outerVariable because innerFunction() "remembers" the scope in which it was created (this is the closure).

Why Closures Are Useful:

  1. Data Encapsulation: Closures can help create private variables. By returning a function that has access to private variables, you can prevent those variables from being modified directly from the outside.

            function counter() {

                  let count = 0;

                  return {

                        increment: function() {

                        count++;

                        return count;

                },

                decrement: function() {

                      count--;

                      return count;

                },

                getCount: function() {

                      return count;

                }

          };

    }

    const myCounter = counter();

    console.log(myCounter.increment()); // 1

    console.log(myCounter.increment()); // 2

    console.log(myCounter.getCount());  // 2

In this example, count is a private variable that can only be modified using the increment() and decrement() methods, which are closures.

  1. Maintaining State: Closures allow you to maintain state between function calls.

            function makeAdder(x) {

                  return function(y) {

                    return x + y; // x is remembered by the returned function

                  };

            }

         const add5 = makeAdder(5);  // Create a function that adds 5 to its argument

        console.log(add5(3)); // 8

        console.log(add5(10)); // 15

        Here, makeAdder creates a closure with the value of x (5), which is then used by the returned function each time it's called.

Closure with Loops:

Closures are also useful when working with loops, especially if you're dealing with asynchronous functions or event handlers.

    for (var i = 0; i < 3; i++) {

          setTimeout(function() {

            console.log(i); // Logs 3 three times due to closure capturing the `i` value after the loop finishes

          }, 1000);

    }

To fix this and capture the current value of i at each iteration, you can use let (which has block-scoping) or an IIFE (Immediately Invoked Function Expression):

    for (let i = 0; i < 3; i++) {

          setTimeout(function() {

            console.log(i); // Logs 0, 1, 2 correctly

          }, 1000);

    }

Alternatively, using an IIFE:

    for (var i = 0; i < 3; i++) {

      (function(i) {

            setTimeout(function() {

            console.log(i); // Correctly logs 0, 1, 2

        }, 1000);

      })(i);

    }

Conclusion:

A closure occurs when a function retains access to variables from its lexical scope even after the outer function has returned. Closures are a fundamental and versatile feature in JavaScript, enabling powerful patterns like data encapsulation, private variables, and maintaining state.

 


No comments:

Post a Comment