0

I know it is a classic js question:

(My question is not how to solve this problem, but how IIFE solve this problem. Thanks for the other answer link but I didn't find the answer I want)

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

This will print out five consecutive 5, and one way to avoid that is to create IIFE in setTimeout, I know it creates a closure but still why? Can someone give a more specific explanation about it?

Also why can't I just pass a parameter to the function?

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

This prints out 5 undefined...I got more confused, why is that?

Keming
  • 233
  • 1
  • 3
  • 11

1 Answers1

3

If I understand your question correctly, you want to print 0...4. You can achieve that by using let instead of var which creates a new binding of i for each loop iteration [1], [2]:

// Prints 0, 1, 2, 3, 4:
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

Your suggestion to add an argument i to the callback fails as the calling function setTimeout doesn't pass anything to the callback. Thus the i argument is undefined.

Alternatively, use the classic IIFE approach:

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

Even better, of course, would be to move the for-loop into the setTimeout callback. But I assume you chose this code for demonstration purposes only.

Community
  • 1
  • 1
le_m
  • 19,302
  • 9
  • 64
  • 74
  • 1
    Thank you for the "let" solution, but I wonder how IIFE solves this problem? – Keming May 19 '17 at 00:05
  • @KemingZeng This answer to the linked question has a good intuitive explanation: http://stackoverflow.com/a/17299889/1647737 – le_m May 19 '17 at 00:05
  • I see, so the key is that IIFE immediately pass the parameter "i" before setTimeout is pushed to the execution queue? But I still don't know why the second one logs undefined – Keming May 19 '17 at 00:12
  • I see your point, in your code, you create an IIFE outside the settimeout function and pass "i" immediately to the anonymous callback function. But if I create an IIFE inside settimeout, make the anonymous function IIFE, it still works, then why is that? – Keming May 19 '17 at 00:21
  • The IIFE is evaluated immediately during each loop-iteration. When evaluating the IIFE, JavaScript binds the IIFE's `i` argument to the **value** (i.e. 1, 2, ...) of the loop-iterator variable `i`. The setTimeout callback is a closure with access to this 'inner' `i`. Now, regarding your `setTimeout(function(i) ...` - this `i` is an argument of the callback. However, setTimeout calls your callback without providing any arguments, i.e. it calls `callback()`. Now, if you call a `function (a) { ...}` without supplying any arguments, then `a` is undefined. This is what happens here to `i`. – le_m May 19 '17 at 00:34
  • Than you for your explanation! – Keming May 19 '17 at 02:10
  • @le_m Can you please explain what do you mean by "Even better, of course, would be to move the for-loop into the setTimeout callback." – Jaideep Sep 14 '19 at 05:00