为什么setTimeout不能取消我的循环?

75
我想知道在Chrome的控制台中,JavaScript的`while`语句可以在一毫秒内增加一个变量多少次,所以我直接将以下代码片段快速地写入了控制台:

我想知道在Chrome的控制台中,JavaScript的while语句可以在一毫秒内增加一个变量多少次,所以我直接将以下代码片段快速地写入了控制台:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

问题在于它一直运行。
这是为什么,我该如何解决?


27
由于 while 循环未将处理器时间让给超时,因此…… - mplungjan
也许阅读这篇文章会有所帮助:https://dev59.com/4mEi5IYBdhLWcg3wPKWx - jillro
4
为什么这个问题被点赞这么多? - user1508519
6
我认为这是一个非常适合展示单线程工作原理的好例子。对于JS来说,人们通常会假设间隔/定时器会立即打断控制流并执行代码,但在某些情况下,比如上文所述,这种情况是不会发生的。 - MickMalone1983
2
作为下面答案的扩展,这是您如何使用延迟递归函数完成您期望的操作:var run = true, i = 0; function loop() { i++; if (run) setTimeout(loop, 0); }; setTimeout(function() { run = false }, 1); loop(); - Izkata
显示剩余4条评论
6个回答

80
这归结于JavaScript的单线程特性1。发生的情况基本上是这样的:
  1. 分配变量。
  2. 计划设置run=false的函数。该函数被安排在当前函数(或其他当前活动内容)运行运行。
  3. 您的无限循环并停留在当前函数内部。
  4. 在您的无限循环之后(从不),将执行setTimeout()回调函数,并将run=false
正如您所看到的,setTimeout()方法在这里行不通。您可能通过在while条件中检查时间来解决此问题,但这会破坏实际测量。 1至少对于更实际的目的,您可以将其视为单线程。实际上有一个所谓的“事件循环”。在该循环中,所有函数都排队等待执行。如果您排队了新的函数,则它将放置在该队列中的相应位置。当前函数完成后,引擎从队列中获取下一个函数(根据由setTimeout()引入的时间),并执行它。 因此,在任何时候只有一个函数在执行,从而使执行非常单线程。有一些关于事件的例外情况,在下面的链接中进行讨论。

供参考:


3
正如@Sirko所提示的那样,要实现你想要的效果,需要将当前时间戳保存在一个变量中,即var start = (new Date()).getTime();。然后在while语句中,从当前时间减去该时间,检查得到的数字是否小于你期望的延迟时间。while ((new Date()).getTime() - start < delay) - Useless Code
1
如果你只是想看看Chrome的运行速度,那么将while循环设置为运行大约10,000次。在循环之前设置一个类似于开始时间的变量,然后在循环之后设置当前时间的变量,最后比较这两个变量。 - SyntaxLAMP
这并不完全是因为JavaScript是单线程的原因。即使这样做非常愚蠢,实现也可以是“在每个指令之间检查消息”,因为JS是一种解释性语言。这是因为事件循环实现,在堆栈为空时调用。 - jillro
@Guilro 关于“有时候”的问题 - 这个概念被称为反讽,我认为我已经通过突出显示的“从不”进行了澄清。至于“消息之间” - 我所知道的每个JS引擎基本上都是单线程的(除了工作者)。请参阅https://dev59.com/BHE85IYBdhLWcg3whT9r#2734311以获取更深入的分析。顺便说一句,大多数现代引擎只有在其更高级的功能失败时才会回退到直接解释JS。 - Sirko
顺便说一下,您链接的问题的答案实际上证明了,在某些情况下,在指令调用之间会立即触发某些事件,而不需要堆栈为空。因此,最好更精确地说明关于事件循环和setTimeout取决于它的内容。 - jillro
显示剩余2条评论

24

JavaScript是单线程的,因此在循环过程中,其他任何东西都不会被执行。


2
当你称呼JavaScript为单线程时要小心,因为它有处理多线程的能力。然而,标准执行是单线程的。 - zzzzBov

19

为了保持 Chrome 的真实速度,而不必经常重新获取时间来计算速度,您可以尝试使用以下 JS 代码:

var start = new Date().getTime()
var i = 0
while(i<1000000)
{
    i++
}
var end = new Date().getTime()
var delta = end-start
var speed = i/delta
console.log(speed + " loops per millisecond")

4

Javascript是单线程的,这意味着它一次只能运行一个指令,按顺序执行。

事件系统,就像许多其他语言和库一样,由事件循环处理。事件循环基本上是一个循环,在每个迭代中检查队列中的消息,并分发事件。

在Javascript中(与实现此模式的大多数语言一样),当堆栈为空时,事件循环被调用,也就是说,当所有函数都返回时,在程序代码的末尾。

幕后,您的“真正”的程序看起来像这样:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

while(true) {
/*
 * check for new messages in the queue and dispatch
 * events if there are some
 */
  processEvents();
}

因此,来自时钟的超时消息从未被处理。

关于事件循环的更多信息请参见: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop


当然,它有点更加复杂,请查看这些示例:Is JavaScript guaranteed to be single-threaded?tl;dr:在某些浏览器引擎中,一些外部事件不依赖于事件循环,并在发生时立即触发,抢占当前任务。但是,setTimeout并不会立即触发,而只是将消息添加到队列中而已。)


2
While循环不会访问setTimeout。你的代码设置了run为true,然后它永远不会变成false。

1
JavaScript拥有单线程和单线程任何地方的特性。
我认为这个问题很好: JavaScript是否保证是单线程的? 当你的代码在循环中时,其他代码不会执行并被阻塞。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接