no_parachute44
no_parachute44

Reputation: 385

Scope and accessing outside variables in anonymous functions?

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();
}

Upvotes: 1

Views: 96

Answers (2)

Andrea
Andrea

Reputation: 566

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]();

Upvotes: 1

user184994
user184994

Reputation: 18281

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)

Upvotes: 1

Related Questions