Closures

A closure in JavaScript refers to the capability of a function to retain access to variables and other resources from the scope in which it was defined, even when the function is executed outside that scope. It allow a function to remember and access its surrounding state, regardless of where and when it is invoked.

In simple words, a closure is a combination of a function and the lexical environment within which that function was declared.

A closure gives us access to an outer function’s scope from and inner function, even after the outer function has finished executing.

function outerFunction() {
    let outerVariable = 'I am in outerFunction().';

    function innerFunction() {
        let innerVariable = 'I am in innerFunction().';
        console.log(innerVariable);
        console.log(outerVariable);
    }
    return innerFunction;
}
var closure = outerFunction();
closure();
I am in innerFunction().
I am in outerFunction().

In the above example, we can make the following observations:

  • innerFunction() is declared inside the outerFunction().
  • innerFunction() has access to outerVariable, which is declared in its lexical scope.
  • Variable closure is assigned a reference to innerFunction(), i.e., the return value of outerFunction().
  • closure function is invoked.

Remember JavaScript treats functions as first-class citizens, which means they can be assigned to variables just like any other value. When you assign a function to a variable (assigning outerFunction() to variable closure), the variable effectively becomes a function reference.

We can see that even after the outerFunction() has finished executing and its local variables have gone out of scope, the closure variable still holds a reference to innerFunction() along with its lexical environment. This is evident by the fact that invocation of closure() results in output of innerVariable and outerVariable to the console.

If variables in parent scope are destroyed, then how can a closure reference them?

When an outer function finishes executing, its local variables are typically destroyed or go out of scope. However, closures maintain references to the value of those variables, not to the variable itself.

Closures work by capturing the entire lexical environment in which they are defined, including all the variables and functions within that scope. This captured environment, which includes the variables that would otherwise go out of scope, is stored along with the closure itself, independent of the original lexical environment or the outer function.

The closure maintains a reference to this captured environment, so even if the outer function’s variables are technically destroyed, they are still accessible through the closure.

The closure first captures its lexical environment, stores it with itself, and then maintains a reference to this captured environment (which is not the original lexical environment).

Does a closure have access to it sibling functions?

No, a closure does not have direct access to its sibling functions. It can call functions residing in its captured lexical environment and get a return value, but it does not have access to the resources of its sibling functions.

The closure captures the variables of the parent function and preserves them, allowing the inner function to access those variables even after the parent function has finished executing.

However, sibling function within the same outer function do not have direct access to each other’s variables or resources through closures. Each function within the same scope has its own separate closure and lexical environment.

If sibling function need to share data or communicate with each other, they typically rely on other mechanisms such as passing arguments, returning values, or using shared variables declared in an outer scope that both functions have access to.

Where are closures used?

Closures are used in various practical scenarios in JavaScript. Some common use-cases where closures are helpful are:

  1. Data Privacy and Encapsulation
  2. Function Factories
  3. Event Handlers
  4. Callbacks
  5. Memoization and Caching
  6. Iterators and Generators

These are just a few examples of scenarios where closures could be helpful. Closures provide a powerful mechanism for managing and preserving the state of functions, thus enhancing code flexibility and re-usability.