Replace
var i = 0
by
let i = 0
and you're done.
A detailed explanation is here.
I'll quote my answer for your understanding below.
Cause of the problem: lack of understanding scope
Check this example to understand the problem:
var
creates function scope
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
While you might expect this forEach
loop to result in number 0
to 9
being printed, instead you get ten times 10
. The cause of this is the variable i
being declared using var
keyword, which creates a function scope
that leads to each function
in funcs
holding a reference to the same i
variable. At the time the forEach
loop is executed, the previous for
-loop has ended and i
holds 10 (9++ from the last iteration).
Compare how ES6's let
, which creates block scope
instead of function scope
, behaves in this regard:
let
(ES6 or officially ES2015) creates block scope
:
var funcs = []
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
Because let
creates block scope
, each iteration of the for
loop has its "own" variable i
.
ES5 solution using an IIFE
wrapper
If you need an ES5 solution, an IIFE (immediately invoked function expression) wrapper would be the way to go:
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value)
}
}(i)))
}
funcs.forEach(function(func) {
func()
})
Here, i
is passed as a parameter to each function which stores its own copy value
.
The same is true for for..in
loops:
var funcs = [],
obj = {
first: "first",
last: "last",
always: "always"
}
for (var key in obj) {
funcs.push(function() {
console.log(key)
})
}
funcs.forEach(function(func) { // outputs: "always", "always", "always"
func()
})
Again, all functions in funcs
hold the reference
to the same key
because var key
creates a function scope that lives outside of the for..in
loop. And again, let
produces the result you'd probably rather expect:
var funcs = [],
obj = {
first: "first",
last: "last",
always: "always"
}
for (let key in obj) {
funcs.push(function() {
console.log(key)
})
}
funcs.forEach(function(func) {
func()
})
Also compare the excellent (!) book
Nicholas C. Zakas: "Understanding ES6", no starch press, p. 8-9.
from which the examples were taken.