JavaScript Closures

A closure is a function that retains access to its lexical scope, even when the function is executed outside of that scope. Essentially, closures allow an inner function to “remember” and access variables from its outer function after the outer function has finished executing.

1. Basic Example of Closure

A simple closure example where an inner function has access to the outer function’s variable:

function outerFunction(outerVariable) {
    return function innerFunction(innerVariable) {
        console.log(`Outer Variable: ${outerVariable}`);
        console.log(`Inner Variable: ${innerVariable}`);
    };
}

const closureFunc = outerFunction('Hello');
closureFunc('World');

Try It Now

Explanation:

  • The inner function innerFunction has access to outerVariable, which was passed to the outerFunction, even though the outerFunction has finished executing.

 

2. Closure with Private Variables

Closures allow us to create private variables by defining them in an outer function and accessing them through inner functions.

function counter() {
    let count = 0; // Private variable
    return function() {
        count++;
        return count;
    };
}

const increment = counter();
console.log(increment()); // Output: 1
console.log(increment()); // Output: 2
console.log(increment()); // Output: 3

Try It Now

Explanation:

  • The variable count is private to the counter function. The inner function has access to this variable, and it keeps its value across multiple calls, demonstrating a private state.

3. Closures with Function Factories

Closures can be used to create functions that retain information between function calls, often used in “function factories.”

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

const sayHello = greeting('Hello');
sayHello('John');  // Output: Hello, John!

const sayGoodbye = greeting('Goodbye');
sayGoodbye('Jane');  // Output: Goodbye, Jane!

Try It Now

Explanation:

  • The greeting function returns a closure that “remembers” the message argument and can be used to create customized greeting functions.

4. Closures in Asynchronous Code

Closures are useful in asynchronous programming, where an inner function can access variables from its outer scope even after asynchronous operations are completed.

function fetchData(url) {
    fetch(url)
        .then(response => response.json())
        .then(data => {
            console.log(data);
        });
}

fetchData('https://jsonplaceholder.typicode.com/posts/1');

Try It Now

Explanation:

  • The inner function inside .then() has access to the variables from its lexical scope, such as response, even after the asynchronous fetch operation completes.

5. IIFE (Immediately Invoked Function Expression) and Closures

An IIFE is a function that runs as soon as it’s defined. It’s commonly used for creating closures and isolating code to avoid polluting the global scope.

const counter = (function() {
    let count = 0;
    return {
        increment: function() {
            count++;
            console.log(count);
        },
        decrement: function() {
            count--;
            console.log(count);
        }
    };
})();

counter.increment(); // Output: 1
counter.increment(); // Output: 2
counter.decrement(); // Output: 1

Try It Now

Explanation:

  • The count variable is encapsulated inside the IIFE and is accessible only through the increment and decrement methods, preventing access to count from the global scope.

6. Closures with Loops

Closures can also capture variables inside loops, which can sometimes lead to unexpected behavior, especially with var inside a loop.

function createFunctions() {
    const funcs = [];
    for (var i = 0; i < 3; i++) {
        funcs.push(function() {
            console.log(i);
        });
    }
    return funcs;
}

const funcs = createFunctions();
funcs[0]();  // Output: 3
funcs[1]();  // Output: 3
funcs[2]();  // Output: 3

Try It Now

Explanation:

  • Since var has function scope, all functions capture the same reference to i. After the loop finishes, i is 3, so all functions print 3.

Fix with let:

function createFunctions() {
    const funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs.push(function() {
            console.log(i);
        });
    }
    return funcs;
}

const funcs = createFunctions();
funcs[0]();  // Output: 0
funcs[1]();  // Output: 1
funcs[2]();  // Output: 2

Try It Now

Explanation:

  • let has block scope, so each iteration of the loop creates a new scope for i, and each closure “remembers” the value of i at the time it was created.

 

7. Common Use Cases for Closures

  • Data Encapsulation: Closures are used to hide implementation details by creating private variables that are not accessible from the outside.
  • Function Factories: Creating functions dynamically based on input values.
  • Callbacks and Event Handlers: Retaining context in asynchronous code, such as handling user events or API responses.

Summary of Closures:

  • A closure is a function that retains access to variables from its outer function even after the outer function has completed execution.
  • Closures are often used for data privacy, function factories, and in asynchronous programming.
  • They allow you to create more modular, flexible, and efficient code by encapsulating state and logic.