Recently, I found myself needing to create an array of functions. The functions use values from an XML document, and I am running through the appropriate nodes with a for l
I recommend picking up a book like JavaScript: The Definitive Guide to gain a deeper understanding of JavaScript in general so you can avoid common pitfalls like this.
This answer also provides a decent explanation on closures specifically:
How do JavaScript closures work?
When you invoke
function() { return i; }
the function is actually doing a variable look-up on the parent call-object (scope), which is where i is defined. In this case, i is defined as 10, and so every single one of those functions will return 10. The reason this works
(function(i){ return function(){ return i;}})(i);
is that by invoking an anonymous function immediately, a new call-object is created in which the current i is defined. So when you invoke the nested function, that function refers to the call-object of the anonymous function (which defines whatever value you passed to it when it was invoked), not the scope in which i was originally defined (which is still 10).
The second method is a little clearer if you use a parameter name that does not mask the loop variable name:
funArr[funArr.length] = (function(val) { return function(){ return val; }})(i);
The problem with your current code is that each function is a closure and they all reference the same variable i
. When each function is run, it returns the value of i
at the time the function is run (which will be one more than the limit value for the loop).
A clearer way would be to write a separate function that returns the closure that you want:
var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = getFun(i);
}
function getFun(val) {
return function() { return val; };
}
Note that this is doing basically the same thing as the first line of code in my answer: calling a function that returns a function and passing the value of i
as a parameter. It's main advantage is clarity.
EDIT: Now that EcmaScript 6 is supported almost everywhere (sorry, IE users), you can get by with a simpler approach—use the let
keyword instead of var
for the loop variable:
var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
With that little change, each funArr
element is a closure bound do a different i
object on each loop iteration. For more info on let
, see this Mozilla Hacks post from 2015. (If you're targeting environments that don't support let
, stick with what I wrote earlier, or run this last through a transpiler before using.
Let's investigate what the code does a little closer and assign imaginary function names:
(function outer(i) {
return function inner() {
return i;
}
})(i);
Here, outer
receives an argument i
. JavaScript employs function scoping, meaning that each variable exists only within the function it is defined in. i
here is defined in outer
, and therefore exists in outer
(and any scopes enclosed within).
inner
contains a reference to the variable i
. (Note that it does not redefine i
as a parameter or with the var
keyword!) JavaScript's scoping rules state that such a reference should be tied to the first enclosing scope, which here is the scope of outer
. Therefore, i
within inner
refers to the same i
that was within outer
.
Finally, after defining the function outer
, we immediately call it, passing it the value i
(which is a separate variable, defined in the outermost scope). The value i
is enclosed within outer
, and its value cannot now be changed by any code within the outermost scope. Thus, when the outermost i
is incremented in the for
loop, the i
within outer
keeps the same value.
Remembering that we've actually created a number of anonymous functions, each with its own scope and argument values, it is hopefully clear how it is that each of these anonymous functions retains its own value for i
.
Finally, for completeness, let's examine what happened with the original code:
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
Here, we can see the anonymous function contains a reference to the outermost i
. As that value changes, it will be reflected within the anonymous function, which does not retain its own copy of the value in any form. Thus, since i == 10
in the outermost scope at the time that we go and call all of these functions we've created, each function will return the value 10
.