The Ultimate Guide to Closures in JavaScript.

Introduction

Closures are a fundamental concept in JavaScript that can be a bit tricky to grasp at first, but once understood, they unlock a powerful feature of the language. In this blog, we'll dive deep into closures, exploring what they are, how they work, and why they're so important.

What is closure

A closure is a function that retains access to its lexical scope, even when the function is executed outside that scope. This means that a function defined inside another function can "remember" the variables from the outer function, even after the outer function has finished executing.

When a function is declared, it has access to variables within its own scope, as well as variables from its parent scope. A closure occurs when a function "remembers" these variables from its parent scope even after the parent function has finished executing.

"Function along with its lexical scope bundled together forms a closure, Closures are created every time a function is created."

function outerFunction() {
    let outerVariable = 'I am outside!';   
    function innerFunction() {
        console.log(outerVariable);
    }   
    return innerFunction;
}
const myClosure = outerFunction();
myClosure();  // Output: I am outside!

In this example, outerFunction creates a variable outerVariable and defines an innerFunction that logs outerVariable to the console. The innerFunction is returned and assigned to the variable myClosure. When myClosure is called, it still has access to outerVariable even though outerFunction has finished executing. This is a closure in action.

Uses of closures

Data Privacy & Hiding: Closures can be used to create private variables. Variables within a closure cannot be accessed from outside the function, which is useful for maintaining data encapsulation.

function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}
const counter = createCounter();
console.log(counter());  // Output: 1
console.log(counter());  // Output: 2

Currying: Closures allow us to create functions with preset parameters, which can be useful for functional programming techniques.

function multiply(a) {
    return function(b) {
        return a * b;
    };
}
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(5));  // Output: 10

Some other use cases of closures are Module Design pattern, Memoization, Maintain state in async world & setTimeout

Closures in Loops

A common pitfall for beginners is using closures within loops. Consider the following example:

for (var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i * 1000);
}

You might expect this to log numbers 1 to 5 with a delay, but instead, it logs the number 6 five times. This happens because the setTimeout function forms a closure that references the variable i, which is incremented to 6 by the time the callbacks are executed.

To fix this, you can use let instead of var:

for (let i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i * 1000);
}

With let, each iteration of the loop creates a new block scope, and i retains its value within each iteration, producing the expected output.

Common Drawbacks of Closures

While closures are powerful, they can sometimes lead to issues if not used carefully:

  1. Memory Leaks: Closures can inadvertently cause memory leaks if they hold references to variables that are no longer needed.

  2. Unintended Variable Sharing: When using closures in loops, variables can be unintentionally shared among all instances of the closure. This can be mitigated using let instead of var or by creating additional closures.

  3. Over consumption of memory

  4. Can freeze browsers if not handled properly.

  5. The closed over variables are not garbage collected till the program expires

Garbage Collection: It is a process of automatic memory management, It removes objects, variables etc that are not in use anymore.

Few Important Points

Function Parameter: Function parameters are closed over as they are part of outer function

Relation of scope Chain & Closures: If outer function is present inside another function It’ll form a closure with that also.

Conflicting name with global variable: Javascript will take value to the first variable it encounters, It’ll check in the outer function scope then in outer & then in global scope. If variable is not found, it’ll throw an reference error

Conclusion

Closures are a fundamental and powerful feature of JavaScript that enable functions to retain access to their lexical scope. They are useful for creating private variables, implementing functional programming techniques, and managing asynchronous code. By understanding closures and their potential pitfalls, you can write more robust and maintainable JavaScript code.