Reputation: 3025
I have this code:
var obj = {"a" : "A", "b" : "B", "c" : "C"};
for( value in obj) {
setTimeout(function() {
console.log(value);
}, 100);
}
The result of running that code in your console is that c
is logged three times.
Why does it always refer to the last key in the setTimeout()
, and how do I get it to refer to them in order inside my for loop?
It's got something to do with scope, but I can't wrap my head around it...
Upvotes: 0
Views: 54
Reputation: 7012
You have a single variable value which is set to an element of obj for each element in obj. Your closure has a reference to that variable, so when value changes in the enclosing scope, it also changes in the closure. Therefore, once the closure is executed after 100 milliseconds, value has been assigned to the last item in obj. To fix this, you need to introduce a new scope by adding another anonymous function, which you pass the value you need to store temporarily:
var obj = {"a": "A", "b": "B", "c":"C"};
for( var value in obj) {
(function(value) {
setTimeout(function() {
console.log(value);
},100);
})(value);
}
or:
var obj = {"a": "A", "b": "B", "c":"C"};
for(var value in obj) {
(function() {
var value2 = value;
setTimeout(function() {
console.log(value2);
},100);
})();
}
You need the paranthesis around the function definition because if "function" is the first token on a line, javascript will treat it as a named function declaration, not a function expression.
Using a closure is the standard way to introduce a new scope in Javascript as of ECMAScript 5. However, in ECMAScript 6 (which is not yet finalized or implemented), you could use a let statement instead.
p.s. You should probably declare value as a var, unless you have done so elsewhere in your function, otherewise you will create a global variable.
Upvotes: 0
Reputation: 9868
An alternative, and sometimes cleaner, way of creating the closure is to make a separate function that returns a timeout function based on the set value:
function getTimeoutFunction(value) {
return function() {
console.log(value);
};
}
for (value in obj) {
setTimeout(getTimeoutFunction(value), 100);
}
Upvotes: 2
Reputation: 19729
You need to use a closure.
var obj = {"a" : "A", "b" : "B", "c" : "C"};
for( value in obj) {
(function(value) {
setTimeout(function() {
console.log(value);
}, 100);
}(value));
}
This is because the value of value
will have changed by the time the setTimeout
callback function will be called.
By wrapping it in an anonymous function the value of value
won't change because a new function will be created for each item in the object — JavaScript has function scope rather than the traditional block scope.
Upvotes: 3