In this quick post we will be covering how to use functions and closures inside a loop in JavaScript so you can solve this issue quickly and without having to look any further!
As with all my Quick Fix Series of posts, you will first find the problem described and then the solution just below.
Let’s get stuck in!
The Problem - JavaScript functions and closures inside for loops
The problem with using closures in JavaScript for loops comes primarily from using the var
statement to declare a variable.
var
statements in non-strict mode are not block scoped which means that their values will leak out into the parent scope as well.
For example consider the following:
var n = 1
{
var n = 10
}
console.log(n) // 10;
Because the var
statement used to declare the variable n
is not block scoped that means that the outer variable that has also been declared as n
then gets re-defined to be 10
even though the value of 10 is being declared in the child block.
So when it comes to using a for loop this should help explain why using a closure inside of a for loop using var is problematic.
Here is another example using a for loop this time:
for (var i = 0; i < 3; i += 1) {
console.log(i) // 0, 1, 2
}
console.log(i) // 3
You might have expected that the last console.log
shouldn’t have logged anything because the variable i
shouldn’t be accessible outside of the scope of the for loop block, but because a var has been used, it is.
Now let’s take a look at the problem when using functions or closures within a for loop now.
const exampleFunctions = []
for (var i = 0; i < 3; i++) {
exampleFunctions.push(() => {
console.log(i)
})
}
for (var j = 0; j < 3; j++) {
exampleFunctions[j]() // 3, 3, 3
}
Now in the above example with our function and the for loop, you can see that after we loop over every function to log what has been stored, it always logs the number 3
and not the numbers 0, 1, 2
.
We can understand why this happens when we remember how var
statements are not blocked scoped.
What this means here is that when we create a function in the first loop, we are not creating the function with the numbers 0, 1, 2
but instead we are creating the function with the variable i
which is just a reference to whatever it was last defined as.
So in the second loop, when we log the variable for each function, it then looks for the variable i
, which as we have seen in the above is now of value 3
so it will always log 3
.
To be more verbose about this here is how this would look in the code if we were not using a for loop:
const exampleFunctions = []
var i = 0
exampleFunctions.push(() => {
console.log(i)
})
exampleFunctions[0]() // 0
var i = 1
exampleFunctions.push(() => {
console.log(i)
})
exampleFunctions[0]() // 1
exampleFunctions[1]() // 1
var i = 2
exampleFunctions.push(() => {
console.log(i)
})
exampleFunctions[0]() // 2
exampleFunctions[1]() // 2
exampleFunctions[2]() // 2
var i = 3
exampleFunctions[0]() // 3
exampleFunctions[1]() // 3
exampleFunctions[2]() // 3
As you can see the variable i
is always referencing the latest version of it which is exactly what you would expect to happen, the only reason why this gets a little confusing is because we expect the function to hold the value of the variable at the time we create the function.
Let’s take a look at some of the solutions to how we can solve this issue.
How to use functions and closures inside a loop in JavaScript
The easiest way to solve this problem is to make use of the let
statement instead of the var
statement when declaring the variable for a for loop.
The reason why this works is because a let
statement, unlike a var
statement, is block scoped. This means that if you define a variable using let in a block it will stay in that block and it won’t pollute the parent scope.
Because of this the only reference to the variables in the for loop is at the time of creation of the function.
Here is an example of using let
to use functions and closures inside a loop in JavaScript:
const exampleFunctions = []
for (let i = 0; i < 3; i++) {
exampleFunctions.push(() => {
console.log(i)
})
}
for (let j = 0; j < 3; j++) {
exampleFunctions[j]() // 0, 1, 2
}
Another solution here is to make use of a closure to be able to protect the value at that point in time so we can reference it for each loop of the for loop.
To do this we just need to create a function using a closure.
Here is an example of how to use a closures inside a loop in JavaScript to solve this issue:
const exampleFunctions = []
const encloseValue = value => () => {
console.log(value)
}
for (var i = 0; i < 3; i++) {
exampleFunctions.push(encloseValue(i))
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
exampleFunctions[j]()
}
Summary
There we have the quick fix to how to use functions and closures inside a loop in JavaScript, if you want more like this be sure to check out some of my other posts!