Closure in JavaScript has a very different meaning than you might initially think, and it's way more fun too.
In this post, you will discover what JavaScript Closure is and how to use it in your programming code. You will also learn more than one way to perform closure in JavaScript. Finally, you will see code examples of how to complete your closure to support everything you have learned.
Let's get started.
What is closure in JavaScript?
Closure in JavaScript is a form of lexical scoping used to preserve variables from the outer scope of a function in the inner scope of a function. Lexical scoping is the process used to define the scope of a variable by its position in the source code. Check out the following video to learn more about JavaScript closures.
When you define a function, you can only access its variables from within the function. Attempting to access variables from outside the function will result in a scope error; this is where closure comes in handy.
Let's look at scope with a code example showcasing variables declared in the global and local scopes.
In the above example, there are two scopes; the first is the global scope, where the variable "message" is declared. The second is the function's local scope, where the variable "audience" is declared. In this code, the function can access the variable "message"; however, "audience" is only accessible to the function. If you try to access the function's local variable, the code will throw an error in response.
In the. example below, we'll try and call the "audience" variable from the global scope, which should result in an errror telling us that this variable is undefined.
This behavior is important because it can help you understand how to avoid errors in your code, and it helps explain how Lexical scoping works. It's interesting to note that Lexical scoping allows a nested scope that can access a variable within its outer function. Let's see what that looks like in the code example below.
Any function declared at this level is technically a nested scope. Each function declared at the global level has its own scope and is not accessible from the global scope. Furthermore, the scope of a function is inaccessible from other functions. Let's look at that in the following code block.
Attempting to run the code above would result in an error message when the greetUser() function gets called.
Once the error occurs, the code will stop running to help keep debugging simple and make it easier to identify the cause of the error message.
More on JavaScript Scope
In the following example, there are two instances of nested scopes; the first is the global scope, named so because it's the highest-level scope.
The global scope is where the message variable is declared. The greetUser() function exists within the buildGreeting() function; this means it's local to its parent function.
JavaScript Closure
As a result of this local scoping, it also means that the greetUser() function is not available from the global scope. However, it's interesting to note that we can access the greetUser() function through the buildGreeting() function.
To make the greetUser() function accessible from the global scope, you return the greetUser() function from within the buildGreeting() function. Then you assign the buildGreeting() function to a variable and call that variable like a function.
There are more ways to create closure in JavaScript, and in the following code block, you can see an example you may find yourself using.
In this example, you create a function called buildGreeting() and return a function that returns a string composed of two variables. The variables get passed in at two points in this code. The first is passed in when assigning the function to a variable, as seen below.
After the assignment, you call the variable a function passing in the following value as an argument for the inner function. In this case, the function call is performed in a console log, allowing you to see the string that the inner function creates.
JavaScript Closures and Loops
Creating closure in JavaScript becomes a little more complicated when working with loops, as it causes undesirable behavior. 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 like with the output below.
"seconds: 0" "seconds: 1" "seconds: 2"
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.
"seconds: 3" "seconds: 3" "seconds: 3"
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 behavior 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.
"seconds: 0" "seconds: 1" "seconds: 2"
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 execute the code, the setTimeout runs as soon as the loop starts, which is the expected behavior. 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.
How to Move Forward With JavaScript Closures
When learning any programming concept, the best way to move forward is to practice what you've learned. Practice is even more critical with closures because the subject matter can be tricky. As a result of this fact, it is beneficial to explore and even create closures to learn what closures can look like in different situations. Each case is a little different, and you can use closures to perform many tasks that would otherwise be much more difficult. Identifying closures is the best way to solidify your understanding of closures and how to create them.