关于javascript:为什么/ setTimeout()代码实际上输出的数字从0到9?

Why does this for / setTimeout() code actually output the numbers from 0 to 9?

JavaScript闭包的常见缺陷是从for循环运行setTimeout(),并期望计数器在每次迭代时以不同的值传递,而实际上它在setTimeout()函数执行之前被赋予最后一个值:

1
2
3
4
5
for (i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i)
  }, 100);
}  // => prints"10" 10 times

对此的一个解决方案是立即调用函数表达式:

1
2
3
4
5
6
for (i = 0; i < 10; i++)
  (function(j) {
    setTimeout(function foo() {
      console.log(j)
    }, 100);
  })(i);  // prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

另一种方法是将一个额外的回调参数传递给setTimeout()(在IE <9中不起作用):

1
2
3
4
5
for (i = 0; i < 10; i++) {
  setTimeout(function foo(n) {
    console.log(n)
  }, 100, i);
}

但为什么以下最简单的代码产生相同的结果(0, 1, 2, ... 9)?

1
2
for (var i = 0; i < 10; i++)
  setTimeout(console.log(i), 100);


出现这种明显令人惊讶的行为是因为setTimeout的第一个参数既可以是函数也可以是字符串,后者是eval() -ed作为代码。

所以setTimeout(console.log(i), 100);会立即执行console.log(i),返回undefined。然后执行setTimeout("", 100),在100ms后进行NOP调用(或由引擎优化)。


why does the following, simplest, code, produce the same result (0, 1,
2, ... 9)?

1
2
for (var i = 0; i < 10; i++)
  setTimeout(console.log(i), 100);

因为它实际上没有。如果仔细观察,您会注意到日志消息在出现在控制台中之前不需要十分之一秒。通过立即调用console.log(i),您只将调用的结果(undefined)传递给setTimeout,后者将不执行任何操作。实际上,代码相当于

1
2
3
4
for (var i = 0; i < 10; i++) {
  console.log(i);
  setTimeout(undefined, 100);
}

如果在所有代码段中替换100i*500,您会发现差异更大,因此日志消息应以半秒的间隔延迟。


只是为了笑容,你可以做的另一件事(当你有.bind())时

1
2
3
4
5
6
for (i = 0; i < 10; i++) {
  setTimeout(function () {
    var i = +this;
    console.log(i)
  }.bind(i), 100);
}

比IIFE少一点点混乱。