Closure in Javascript
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
Using closures in your code
Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.
Closure scope chain
Every closure has three scopes:
Local scope (Own scope)
Enclosing scope (can be block, function, or module scope)
Global scope
Further, let's also explore how we can use closure to define a function (an inner function) n inside another function m (an outer function) and expose n. Let us see how we will do so below with a quick example:
Creating closures in loops: A common mistake
There are two main disadvantages of overusing closures:
The variables declared inside a closure are not garbage collected. Too many closures can slow down your application. This is actually caused by duplication of code in the memory.
Creating closure in JavaScript becomes a little more complicated when working with loops, as it causes undesirable behaviour. For example, consider the following function, which uses a setTimeout function within a loop.
In the above code, the loop runs three times, and the setTimeout function waits for a specified time to pass before running the code within it. You might expect the code to run three times, reflecting the id's index value at that loop's time.
However, in this case, the loops run, and the id variable updates accordingly. Since the code runs from the setTimeout function, the id has already been updated to its maximum value. Because all three iterations of the loop share the same scope, the setTimeout function creates a closure shared by each loop. This fact means that the message printed is not what you expect. Instead, the console log reflects the final value of the id.
ES6 let Keyword
You can alleviate this issue, you can use the JavaScript ES6 let keyword to ensure the code within the if block runs as expected. A new scope gets created for each loop iteration using the let keyword to declare the index value. Let's see how you can do this in the example below.
The code above results in the expected behaviour instead of the setTimeout function running after the loop completes. The loop runs, and the setTimeout function gets assigned with the id to each iteration of the loop. You can see the resulting output below.
IIFE and Closures
Another way to avoid this issue with closures in a loop is to use the IIFE (Immediately Invoked Function Expression) syntax, which forces an immediate invocation of the setTimeout function as soon as the loop runs. So instead of essentially stacking the setTimeout function and waiting for the loop to finish, then executing the code, the setTimeout runs as soon as the loop starts, which is the expected behaviour. Let's see what the syntax for IIFE looks like below.
In the above code, the loops run, and the function is invoked immediately on each iteration. The setTimeout function then starts to execute immediately, preserving the state of the id in each iteration. However, it is worth noting that the ES6 approach is a much cleaner solution to this issue; however, there may be times when IIFE works better.
Closures
Closures in JavaScript are a powerful and often misunderstood concept. In simple terms, a closure is an inner function that has access to variables from its outer (enclosing) function, even after the outer function has finished executing. This means that the inner function "closes over" the variables in its lexical scope, preserving their values.
Practical use cases where closures are beneficial include:
Data Privacy: Closures help create private variables and functions in JavaScript. The variables within the outer function are not directly accessible from outside, but can be accessed and modified through the inner function, providing encapsulation and data privacy.
Function Factories: Closures enable the creation of function factories, where a function returns another function with predefined behavior. The returned function retains access to the variables in the outer function, allowing for customized behavior based on the initial configuration.
Event Handlers: Closures are commonly used in event handling. Event handlers are often defined within other functions, allowing them to access the surrounding context and work with specific data related to the event.
Memoization: Closures can be used for memoization, which is a technique to optimize function performance by caching the results of expensive function calls. The closure retains the cached values, reducing the need for repeated computations.
Closures are a powerful tool in JavaScript that enables the creation of flexible and encapsulated code structures. Understanding closures is crucial for advanced JavaScript development and can be leveraged to solve various programming challenges.