0

I am using Immediately-Invoked Function Expression to use setTimeout in while loop.

This is my code:

let div = document.querySelector('div')
let x = 0;
while(x<10){
    (function(x){
    setTimeout(function(){
        div.textContent =x;
    },1000*x)
})(x++)
}
<div></div>

let div = document.querySelector('div')
let x = 0;
while (x < 10) {
  (function() {
    setTimeout(function() {
      div.textContent = x;
    }, 1000 * x)
    x++;
  })()
}
<div></div>

The code works fine, but I just curious why my second codes doesn't work and will make the page either become unresponsive or doesn't work at all.

I do check several answer about IIFE like this and this, but seems not solving my problem.

Could anyone explain me the difference putting x++ in the () and putting x++ inside the function?

What is the difference between them?

Thanks for any responds!

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
James
  • 2,732
  • 2
  • 5
  • 28
  • 1
    not sure what you mean my make the page become unresponsive. It just makes the text jump straight to 10 - and yes I can explain why it does this but not in the working example. (It's to do with the scope of `x`.) I guess this is what you mean by "doesn't work at all" but that's not very precise. As for unresponsive, as I said - no idea where you're getting that from. – Robin Zigmond Dec 30 '21 at 12:44
  • @RobinZigmond, not sure if it is my problem, but sometimes I run my second code, the page just keep loadind and loading and finally become unresponsive – James Dec 30 '21 at 12:50
  • 2
    possible duplicate of https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – Salman A Dec 30 '21 at 12:54

1 Answers1

2

The variable x refers to different things in both examples when performing div.textContent =x;. Starting with your second example first, the value of x when performing div.textContent =x; is obtained from the outer scope of your setTimeout callback function, ie, your global let x = 0; declaration:

let div = document.querySelector('div')
let x = 0; <-----------------+
while (x < 10) {             |
  (function() {              |
    setTimeout(function() {  |
      div.textContent = x; --+
    }, 1000 * x)
    x++;
  })()
}

The above loop iterates 10 times, each iteration it:

  1. Invokes the IIFE

  2. When the IIFE runs, it queues a callback using setTimeout(). This callback is only run after your synchronous while() loop has completed all of its iterations

  3. Increments x after queuing your setTimeout calls.

Point is 2 is the main point here. Because all of your callback functions queued by each setTimeout() call are only invoked after your while loop has completed, x at the time that your callback executes will be set to 10. As outlined above, your callback functions all refer to the x declared in the global scope (which is now 10), and so your HTML content will only update with the value of 10.

For your second example, your x refers to the argument of the IIFE, and not the globally declared variable. When you invoke your IIFE, you're passing in the current value of x that you're iterated on and then incrementing it (with x++). This allows the body of the IIFE to have its own independent "copy" of x that is scoped locally to the IIFE body, and can't be accessed from outside the IIFE. The callback in the setTimeout() references this local version of the x and not the global one. It might be clearer if you call the formal parameter of your IIFE something other than x (such as y) to highlight that incrementing x from the outer scope doesn't influence the x from within the inner scope of IIFE:

while(x<10){
  (function(y){ <------------+
    setTimeout(function(){   |
      div.textContent =y; ---+
    },1000*x)
  })(x++)
}

For each iteration of the while loop:

  1. You invoke your IIFE, passing in the value of x as an argument, and then incrementing it using x++

  2. The IIFE runs, creating a new scope with local variables of its own. In this case y (in your example x), is local to the IIFE and takes the value of the x value that was passed into the function when it was called.

  3. A call to setTimeout() queues a callback. The queued callback is only after your loop has completed.

The main difference in this example as opposed to the first is that the callback here references the local y variable for each function IIFE created. So even though the callbacks are called after your while loop has finished and the global value for x is now 10, each setTimeout() callback will refer to the local y variable created when the IIFE was invoked, where y refers to the value of x for that particular iteration.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • Appreciate for the detailed explanation. Just one question to the example you give (example that change x to y), the `y` we defined in `function(y)` is a duplicate of global x and it is not a new local variable we defined, so the value of y will be changed as global x changed. Is it correct? – James Dec 30 '21 at 15:16
  • 1
    @James `y` _is_ the new local variable that we defined, so the value of `y` will not be changed as the global `x` is changed as `y` is a "copy" of the global `x` variable. If `y` were to change as the global `x` changes, then you would see the same issue with all the values being `10`, but that doesn't happen, as `y` isn't changed by anything in the above code, it is only read – Nick Parsons Dec 30 '21 at 22:21