The closure is one of the most important concepts alongside a confusing keyword in the world of javascript.
The closure is created every time a function gets created at function creation time.
It gives access to an outer function from the inner function.
Let's try to dive into the concept and understand what exactly are closures and where it comes into play.
The definition of MDN, says "A closure is the combination of a function and the lexical environment within which that function was declared."
Closure is when a function can remember and access its lexical scope even when it's invoked outside its lexical scope.
Here, we need to understand the "lexical scope".
Lexical scope states how a variable name is resolved within nested functions.
Let's outline a basic closure.
outerFunc() creates a local variable called outer and a function called innerFunc(). The innerFunc() the function is an inner function that is defined inside outerFunc() and is available only within the body of the outerFunc() function. Note that the innerFunc() function has no local variables of its own.
However, since inner functions have access to the variables of outer functions, innerFunc() can access the variable outer declared in the parent function, outerFunc().
This is an example of lexical scoping, which describes how a parser resolves variable names when functions are nested. The word lexical refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available.
In some programming languages, the local variables within a function exist for just the duration of that function's execution. Once outerFunc() finishes executing, you might expect that the outer variable would no longer be accessible.
However, because the code still works as expected, this is obviously not the case in JavaScript.
The reason is that JavaScript creates a "Closure". In the above case, myFunc is a reference to the instance of the innerFunc that is created when the myFunc gets executed.
The instance of innerFunc maintains a reference to its lexical environment, within which the variable name "outer" exists. For this due reason, when the myFunc is invoked, the variable "outer" remains available for use.
Let's take a slightly more complex example.
Can you guess what's going on with this code?
When you are trying to invoke the add5() method, it has reference to the anonymous function which accepts an argument "y" but makeAdder() accepts an argument "x". So, how this lexical scope is getting created.
Let's understand this.
If you see the add5() and add10() method creates a closure.
If you try to console the add5 method, it will display as below.
function(y) {
window.runnerWindow.proxyConsole.log(y)
return x + y;
}
The anonymous function still holds the value of "x" in the outer scope created from closure.
I hope by this point, you might have started making sense of how tricky the concept of "Closures" is but at the same time it is also interesting.
Let's talk about the most common mistakes, developers make while understanding the closures.
We will take a most asked interview questions on the concept of "Closures", i.e. the canonical for loop example.
At the first glance, if you are learning "Closures" for the first time, most of the answers you will get are like.
But, what actually you will get is like.
Now, the point here to ponder is Why? Let's talk about a few possible solutions.
- Using the "let" keyword.
Using the let keyword, every closure binds the block-scoped variable, meaning that no additional closures are required.
- Using the "IIFE".
If you see, we are using an IIFE function that takes an argument "index" which holds the current value of "i" at every iteration.
Closely analyzing the IIFE block, the setTimeout() function holds the value of "index" even after it has been executed which was created using a "closure".
That is the magic of using a "Closure".
- Returning a "function" just like a "closure".
If you understand the above example, the solution here is self-explanatory, the setTimeout() function holds the current value at every iteration. As we are returning a function with IIFE too, it prints the desired output.
Let's take another example of Closure.
I guess, now you would be able to make what should be the output?
If you answer is above, then let's understand what's going on?
If your answer is 1,2,3,4. I guess, you are on the right track.
So, let's try to understand what's going in the code block.
Always remember this point.
Closures - It is created when functions are created, not when they are invoked.
Let's see when we are invoking the getItems0 on line # 13, let's start executing the code block.
An empty array is initialized on line #2.
Then the for loop on the line #3, variable "i" is hoisted. In other words, the "i" variable is declared outside of the for loop, and the variable is then mutated during each iteration.
For each iteration, we're pushing an anonymous function to the array. You can push a value, an object or a function to an array. If we pushed i to the array, we would push the value of i at the time of iteration to the array.
As we are pushing an anonymous function to the array, the anonymous function will return the value of "i" at the time of invocation. At the time of the invocation, the loop would have already run.
By the time the loop completes, "i" will be equal to 5 because of the way that the increment expression in a for loop works.
The only left out question is how can each anonymous function in the getItems array can access the value of i at each iteration?
The definite answer is to bind the value of "i" at each iteration and that too at the creation time and not the invocation time ???
Think about what's happening when we execute the anonymous function.
- We're invoking an anonymous function from the global scope.
- addItems on line 1 is an outer function that returns a bunch of inner functions, because the items array that is returned contains a bunch of anonymous functions that are essentially an array of inner functions in the addItems function
- Each of these inner functions in the items array is accessing the i variable which is in the addItems function scope
- At the time that each inner function is invoked, the addItems function no longer exists, yet each of the inner functions maintains access to the i variable that is in the addItems scope
So, all the above conditions for a closure have been met.
Now, only part left is to uniquely bind the "i" variable at each iteration.
We can solve using "let" keyword and IIFE.
Using "let" keyword.
Using "IIFE"
By wrapping each items.push in an IIFE, we are ensuring that the j variable is privately scoped to this function, and as a result, the j variable will have a unique binding for each iteration.
I hope the above examples will help you a lot in understanding the "Closures".
Two important characteristics of determining a Closure.
- An outer wrapping function is invoked, to create the enclosing scope.
- The return value of the wrapping function must include reference to at least one inner function that then has closure over the private inner scope of the wrapper.
Let's see some practical use of the "Closures".
- Encapsulation - Emulating private methods
- Common web examples like "Adding buttons to a page to adjust the text size".
- Module pattern and Revealing Module pattern
- Closures can be used to make functions more memory efficient and performant - for example, ensure that a large array/object is only initialized once.
- Closures are the fundamental concept of the functional programming. HOF, currying is not possible without closures.
- Init functions - closures can be used to ensure that a function is only called once.
I hope this article might have cleared most of the misconceptions around "closure". If you really like the article, please follow me.
Happy coding. Keep learning. Keep exploring.
Top comments (0)