for循环内的setTimeout导致错误的输出(背后的理论)

luk*_*act 2 javascript settimeout

我一直在查看 StackOverflow 上已经发布的答案,但没有一个是令人满意的。他们都给出了问题的实际解决方案,但这并不能解决我的问题。

看看这个例子:

for(var i = 1; i <= 5; i++) { 
  setTimeout(function(){ 
    console.log(i) 
    }, 1000); 
}
Run Code Online (Sandbox Code Playgroud)

这是一个经典的例子,它总是会产生:

6
6
6
6
6
Run Code Online (Sandbox Code Playgroud)

这一切都很好。我的理解是 for 循环运行并设置 5 个超时以在 1 秒后运行。当 1 秒过去后,setTimeout 内的函数将运行。由于这是 1 秒,因此我很快就会得到值 6。

所以我尝试了别的东西。如果我将超时设置为 0 毫秒会怎么样?这会改变结果吗?


for(var i = 1; i <= 5; i++) { 
  setTimeout(function(){ 
    console.log(i) 
    }, 0); 
}
Run Code Online (Sandbox Code Playgroud)

不,没有。

6
6
6
6
6
Run Code Online (Sandbox Code Playgroud)

这毫无意义。为什么异步 setTimeout 函数只有在for 循环完成后才运行。我什至尝试设置 for 循环值 i <= 50000,但结果仍然相同。这毫无意义。在我看来,一旦 1ms 过去,setTimeout 就会调用该函数,无论它是否在 for 循环内。有人可以向我解释一下吗?谢谢,我读了无数的媒体、stackoverflow 和博客文章,但仍然没有点击。范围解释也没有帮助。

Uda*_*ale 5

因为var是吊装的。var当您在循环中定义 a 时for,该变量将在循环顶部定义for

var i = undefined;
for(i = 1; i <= 5; i++) { 
  setTimeout(function(){ 
    console.log(i) 
    }, 1000); 
}
Run Code Online (Sandbox Code Playgroud)

的回调函数setTimeout与变量形成一个闭包i。当执行回调时,闭包就被解决了。由于每次for迭代都会注册一个回调,该回调将在循环退出后进行计算for,因此 的值i就是6此时的值。

您应该使用它letvar不是块作用域且不提升的。receive的每次迭代for都有自己的迭代i,它将与回调形成闭包。这样,您将收到想要的结果。

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


1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

简而言之,事件循环

事件循环是浏览器(或 Node)中的一个在后台持续运行的软件。它的工作是向 JavaScript 引擎(例如 Chrome 中的 V8)提供回调函数来执行。该setTimeout函数是浏览器提供的 Web-API,它将回调函数发送到(事件队列)任务队列以在 JavaScript 引擎内执行。

事件循环从任务队列中选择一个回调并将其推送到 JavaScript 引擎的调用堆栈上,但它只能在调用堆栈为空时执行此操作。每次for循环迭代都会将setTimeout调用推送到执行它的调用堆栈。因此,除非完成所有 for 循环迭代,否则调用堆栈永远不会为空。因此,任务队列中排队的所有回调最终都会被执行。您选择什么时间打电话并不重要setTimeout

我写了这篇Medium 文章,详细讨论了事件循环。

链接