1

new to javascript.

I have a a custom defined object with a method update. I have an array of the objects.

  function update_all(objects){
    for (var i = 0 ; i < objects.length; i++){
       var obj = objects[i];
       var repeater = setInterval(function () {obj.update();}, 1000);
    }
  }

curiously, only the last object of the array get a setinterval for the objects update method. Also all the setintervals point to the last object of the array. So for example if there are 4 objects, the last object updates 4 times a second, the others not at all. what's going on?

thanks

7
  • Just FYI, it's faster to work out objects.length before the loop, as it is recalculated on each iteration if called during the loop's creation. Commented Feb 13, 2014 at 12:37
  • You have to use closures to pass the data to the inner function, DEMO at jsfiddle.net/M4Xkh/1 Commented Feb 13, 2014 at 12:40
  • @Grim... no it's not. Commented Feb 13, 2014 at 12:41
  • @Benjamin, maybe Grim's statement could be rephrased like so to prevent arguments: "...objects.length before the loop, as it might be evaluated on each iteration depending on the JavaScript interpreter" Commented Feb 13, 2014 at 12:47
  • 1
    @chiccodoro yeah I guess it might, but it really really really really won't, promise. Technically an interperter could do Thread.Sleep(1000) every time an object length is accessed but none do. Caching array.length is not faster in any JavaScript interperter from the last few years and it just adds clutter. On the other hand, caching the lengths of DOM NodeLists is important (this is a rather common misconception) as NodeLists are live - this means that if you get elements with getElementsByTagName and append new elements with that tag name in the loop you get an infinite loop. Fun Commented Feb 13, 2014 at 12:51

3 Answers 3

3

In JavaScript, a for loop does not open a new scope for variables. Your obj variable has at least function scope (if your code is in a function, or otherwise global scope). That is, each iteration through the for loop in fact changes the value of the single obj variable in the function. When you leave the for loop, you end up with a lot of timers, but they all call update on the same object.

To prevent that you need to extract your loop body into another function so as to create a new scope for each iteration. Alongside with some other recommendations, it could look like this:

var i, n;

for (i = 0, n = objects.length; i < n; i++) {
    createIntervalToCallUpdate(objects[i]);
}

And your function:

function createIntervalToCallUpdate(objectToUpdate) {
    var repeater = setInterval(function () { objectToUpdate.update(); }, 1000);
}

To get more understanding on this, read about a concept called "hoisting", for example in this article about scoping and hoisting. Hoisting is also a reason why many recommend to declare all your variables at the top of the function. If you declared obj at the top of your function (which implies, outside the for loop), you wouldn't be trapped into believing that the variable could be redefined for each iteration.

Update: Philipp's suggestion is great as it makes the code simpler and easier to read (given that your browser supports array.forEach which is rather new). It basically does exactly what the code above does, except it already does the task of the first snippet for you.

Sign up to request clarification or add additional context in comments.

Comments

3

The variable obj has the same scope for all iterations. You could fix that problem by replacing your for-loop with the array.forEach function.

 objects.forEach( function(obj) {
      setInterval(function () {obj.update();}, 1000);
 });

The forEach function calls the declared function once for each entry in the array. The anonymous function defines a closure in which each obj represents a different instance.

4 Comments

+1, this is the prefered and simplest approach. objects.forEach(setInterval.bind(null,obj.update.bind(obj),1000)) if you're into a more functional style
does forEach supports ie8
@Basha Not by default, but there is a polyfill under the link I posted in the answer.
Yes, I think we should use this approach in modern browsers.
0

What you're experiencing has to do with closures. The variable obj is not only accesible inside the for-loop, which means that when the callback of your setInterval is executed, it will the last reference of obj that is availible - in this case the last object in the array.

To fix this problem, you could try using the built-in function forEach in combinaion with a function for setting the interval:

function setObjectInterval( obj ) {
    setInterval(function () {obj.update();}, 1000);
}

objects.forEach( function( obj ) {
    setObjectInterval( obj );
} );

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.