While reading a different topic I came across this piece of code:

function loadme() {
 var arr = ["a", "b", "c"];
 var fs = [];
 for (var i in arr) {
   var x = arr[i];
   var f = function() { console.log(x) };
   f();
   fs.push(f);
 }
 for (var j in fs) {
   fs[j]();
 }
}

This will output: a,b,c,c,c,c. The problem here as explained by the author is that the x var is hoisted at the start of the function and therefore does not keeps it's value when it is used in the loop. What I do not understand is why is c assigned to x on the second console.log for 3 times? Can someone explain?

有帮助吗?

解决方案 2

var x = arr[i];
var f = function() { console.log(x) };

In these two lines, console.log(x) thinks that it just has to print x. So, all the three functions are created that way only. So, when you immediately execute the functions, the value of x is different in each of the iterations. But when the looping is over, the variable x retains the last value it held and when the dynamically created functions are executed, they simply print the value of x which is c.

So, in order to fix this, you have to have your own copy of x, for each dynamically created function. Normally, we retain the current state of the variable which changes in the loop, with a function parameter, like this

var f = function(x) { return function() { console.log(x) } }(x);

Now, we are creating a function and executing it immediately, which returns another function and that returned function actually prints the value of x.

function(x) {
    return function() {
        console.log(x)
    }
}(x);

We are passing the value of x as a parameter to the wrapper function and now the current value of x is retained.

其他提示

This situation is, in fact, quite easy to explain and it occurs in many programming languages, not just JavaScript.

Let's first take the output sequense a,b,c,c,c,c and discard the first three elements - a,b,c because this is what gets printed when you execute your function f() inside the first for loop.

So, we're left with the sequense c,c,c which gets printed when we iterate over the fs array. The reason why you don't get a,b,c printed is because the x parameter that you have inside your first for loop is captured by reference (not sure if the definition is valid, though) by your f() closure (remember, every function in JavaScript is a closure of some sort).

Now, since the console.log(x) expression is evaluated when you call the f(), the current value of the captured x parameter will be passed as an argument. While the code works, as seems to be expected, when you invoke f() inside the first for loop, the x is what has just been assigned from arr[i] so you get a,b,c. But when you exit the loop, the x (even though it's unavailable to the outer scope) is still captured by f() but after the first loop it has a value c (the one that it got on the last iteration) and that is what gets evaluated when you iterated over your functions the second time.

This is, in fact, can be a source of many confusing bugs and so some languages can detect this and notify the developer (for example, C# compiler, when it sees constructs like this, produces the following warning: Access to modified closure).

To fix the problem, you just need to capture the current value of x inside your first for loop:

for (var i in arr) {
    var x = arr[i];

    var f = (function(x) {
       return function() { console.log(x) };
    })(x);

    f();
    fs.push(f);
}

Here we use IIFE (Immediately-Invoked Function Expression) to capture the current value of x.

Hope this helps.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top