Mutable variable is accessible from closure. How can I fix this?

In ecmascript 6 we have new opportunities.

The let statement declares a block scope local variable, optionally initializing it to a value. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let


I liked the paragraph Closures Inside Loops from Javascript Garden

It explains three ways of doing it.

The wrong way of using a closure inside a loop

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

Solution 1 with anonymous wrapper

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

Solution 2 - returning a function from a closure

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!

for(var i = 0; i < 10; i++) {
    setTimeout(console.log.bind(console, i), 1000);
}

I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).

p.s. if your brain didn't melt you haven't had enough Javascript that day.


You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:

function (item) { // what to do when item is selected
    comp = me.map[item];
    if (typeof comp === 'undefined') {
        return this.query;
    }

    window.location.href = me.format(gotoUrl, comp.s, target.destination);

    return item;
}

With this:

(function (inner_target) {
    return function (item) { // what to do when item is selected
        comp = me.map[item];
        if (typeof comp === 'undefined') {
            return this.query;
        }

        window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);

        return item;
    }
}(target))

Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.

(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)