1

I'm sure I'm doing something dumb here, but I'm not sure what. I'm adding anonymous functions to an array for later execution, and creating them in a for loop with variables that change each iteration.

I'm expecting the logged values to be: https:server.net/a https:server.net/b

but instead I'm getting: https:server.net/b https:server.net/b

It looks like maybe when I redefine the function it overwrites the last version of it, but I'm not sure why. I would think that each anonymous function would be different.

Why? What am I doing wrong here?

Here's some sample code:

f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
    var itemID = items[i];
    var itemAccessUrl = `https://server.net/${itemID}`;

    var func = function(){ 
        console.log(itemAccessUrl);
    };
    f.push(func);
}

console.log(f.length);
for(var j = 0; j < f.length; j++){
    func();
}
no_parachute44
  • 365
  • 1
  • 15
  • Possible duplicate of [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Adam Jenkins Oct 24 '17 at 18:05

2 Answers2

1

This is because of the nature of var scoping. var is not block scoped, it is "hoisted", as if you declared it at the top of your function.

You could declare itemAccessUrl using let instead of var, which is block scoped, but it depends on what browser support you require. (Having said that, you're using templated strings, so you should be fine)

user184994
  • 17,791
  • 1
  • 46
  • 52
  • If I change itemID and itemAccessUrl to let's instead of var's, the problem persists. If I change it for func, when I execute it later I get the error: "function is not defined". Is there some other way I need to call it? – no_parachute44 Oct 24 '17 at 18:15
  • That's from the line in your second for loop. Change that to `f[j]();`. Othewise, you would be calling the same function, just several times in a loop – user184994 Oct 24 '17 at 18:16
1

What's happening here? When you invoke your array's functions, they use the current value of itemAccessUrl, i.e. the last assigned string, with char 'b'. This because their invocation happens after the completion of first for-loop.

You can use a closure:

f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
    var itemID = items[i];
    var itemAccessUrl = `https://server.net/${itemID}`;

    var func = (function(param) {
        return function () {
            console.log(param);
        };
    })(itemAccessUrl);
    f.push(func);
}

console.log(f.length);
for(var j = 0; j < f.length; j++){
    f[j]();
}

or bind your function to current param:

f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
    var itemID = items[i];
    var itemAccessUrl = `https://server.net/${itemID}`;

    var func = (function (param) {
        console.log(param);
    }).bind(null, itemAccessUrl);
    f.push(func);
}

console.log(f.length);
for(var j = 0; j < f.length; j++){
    f[j]();
}

Furthermore you have to change the second loop, invoking f[j]();

Andrea
  • 526
  • 1
  • 6
  • 20