1

guys! I am a total newbie in javascript. I wrote my first program and already stuck with it's behavior:

var elements = document.getElementsByTagName("img");
for (var i = 0; i < elements.length; i++) {           
    if (i % 2 == 0) {
        elements[i].src = "img.jpg";
        for (var j = 0; j <= 1; j += 0.1) {
            setTimeout(increase_opacity(elements[i], j), 2000);
            // setTimeout(alert(j), 2000);
        }
    }
}

function increase_opacity(element, opacity) {
    element.style.opacity = opacity;
}

I can not see that opacity changes, but under debugger it does, so setTimeout just doesn't work. If I uncomment // setTimeout(alert(j), 2000); I can see both opacity change and alert message on every cycle step. Why is that?

Qué Padre
  • 2,005
  • 3
  • 24
  • 39
  • read about closures. f.e. [here](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work) – Kirill Apr 23 '14 at 13:21
  • @Kirill well, I almost got it, but I still don't understand why this works if I uncomment // setTimeout(alert(j), 2000);... – Qué Padre Apr 23 '14 at 13:38

3 Answers3

1

setTimeOut accepts a function, and what you were doing is passing it the result of a call to increase_opacity. Code below should work for you. Notice how increase_opacity is wrapped inside of the anonymous function definition.

var elements = document.getElementsByTagName("img");
for (var i = 0; i < elements.length; i++) {           
    if (i % 2 == 0) {
        elements[i].src = "img.jpg";
        for (var j = 0; j <= 1; j += 0.1) {
           (function(element, opacity) {
              setTimeout(function() { increase_opacity(element, opacity) } , 2000);
            })(elements[i], j);

            // setTimeout(alert(j), 2000);
        }
    }
}

Here is the doc on setTimeOut http://www.w3schools.com/jsref/met_win_settimeout.asp

milagvoniduak
  • 3,214
  • 1
  • 18
  • 18
  • 1
    It still won't work as expected because the anonymous function will capture i and j by reference and in ALL delayed calls increase_opacity will only be called with the last values of i and j. One of the many Javascript quirks -> opacity will jump instead of fade. – Matthias247 Apr 23 '14 at 13:22
  • Yes, It doesn't work and if this was a reason why everything is okay if I uncomment the commented statement? – Qué Padre Apr 23 '14 at 13:23
  • Probably this can be worked around with using `let` instead of `var` for the loop indexes. The other possibility is to create an individual anonymous function in each loop iteration (by calling a function which returns a function) that uses the desired i and j and pass that to setTimeout – Matthias247 Apr 23 '14 at 13:24
  • Edited the answer, save element and opacity to local variable inside of the for loop. – milagvoniduak Apr 23 '14 at 13:30
  • @MyP3uK Why the situation is different if I uncomment the commented statement? – Qué Padre Apr 23 '14 at 13:37
  • do you mean this statement -> // setTimeout(alert(j), 2000); ? – milagvoniduak Apr 23 '14 at 13:39
  • @MyP3uK: Saving to local variables still won't work. In Javascript the scope of a local variable is the function, not the block, so all your timeouts are still referencing the same variable values, whatever values they had the last time through the loop. – Russell Zahniser Apr 23 '14 at 13:43
  • Because in that case when you pass increase_opacity to the setTimeout function you evaluate it on the spot not in 2 seconds, same with the alert. The reason why you see opacity changing is because when you invoke alert it block the execution of the script and you are able to observe gradual change in opacity. – milagvoniduak Apr 23 '14 at 13:44
  • @RussellZahniser you are correct it needs to be wrapped in a closure to save the values. – milagvoniduak Apr 23 '14 at 13:48
1

You need to pass a function to setTimeout(). What you are doing is calling increase_opacity immediately and then passing the return value to setTimeout().

Since you want to call the function with particular arguments, you can use a closure to record the current values of i and j:

setTimeout((function(a, b) {
  return function() {
    increase_opacity(a, b);
  };
})(elements[i], j), 2000);
Russell Zahniser
  • 16,188
  • 39
  • 30
0

Are you aware that setTimeout() is not a blocking call? Your script does not stop at the setTimeout() function, but continues its execution. So here's what's happening here:

1) Your code starts the loop;
2) When it enters a setTimeout(), it launches the timer it and goes ahead;
3) Repeat step 2 for every element; (Please note that steps 2 and 3 are executed in a few ms)
4) All the timers expires, all the instructions are executed all at once.

To avoid this, you should write something like:

var elements = document.getElementsByTagName("img");
for (var i = 0; i < elements.length; i++) {           
    if (i % 2 == 0) {
        elements[i].src = "img.jpg";
        setTimeout(function(){increase_opacity(elements[i],0);}, 2000);
    }
}

function increase_opacity(element, opacity) {
    element.style.opacity = opacity;
    if (opacity < 1) setTimeout(function(){increase_opacity(element, opacity+0.1);},2000);
}

NB: When dealing with setTimeout() you should avoid that notation when calling function with parameters. You should always use this:

setTimeout(function(){yourFunctionName(arg1, arg2, arg3);});

NB 2: setTimeout(alert(j),2000); is not even waiting 2 seconds to appear. Like I said above, you should use the anonymous function when dealing with setTimeout(). It's probably giving the impression it's working because of this, but it's a sort of glitch IMO.

Vereos
  • 503
  • 4
  • 15