不延迟调用setTimeout

63

经常在 JavaScript 库的代码中看到这样的写法:

setTimeout(function() {
    ...
}, 0);

我想知道为什么要使用这样的包装器代码。


33
专业提示:您不需要在末尾加上“,0”。 - vsync
5个回答

108

非常简单:

浏览器是单线程的,这个单线程(UI 线程)由渲染引擎和 JS 引擎共享。

如果你想做的事情需要很长时间(我们在这里谈论的是循环,但仍然如此),它可能会暂停(暂停)渲染(流程和绘制)。

在浏览器中还存在“桶”,其中所有事件都首先等待 UI 线程完成其任务。一旦线程完成,它就会查看桶并选择排在最前面的任务。

使用 setTimeout 可以在延迟后在桶中创建一个新任务,并在线程可用于更多工作时处理它。

一个故事:

0 毫秒延迟后创建一个新函数任务,并将其放入桶中。此时 UI 线程正在忙于其他事情,而桶中已经有另一个任务了。6 毫秒后,线程空闲并获取排在你前面的任务,很好,轮到你了。但是怎么回事?那是一个巨大的东西!已经过去很久了(30 毫秒)!

最后,现在线程完成了,来取得你的任务。

大多数浏览器都有一个最小延迟,超过 0,因此将 0 作为延迟意味着:尽快将此任务放入篮子中。但是告诉 UA 尽快将其放入桶中并不保证它会在那个时刻执行。桶就像邮局一样,可能有很长的其他任务队列。邮局也是单线程的,只有一个人帮助所有任务...对不起,客户处理他们的任务。和其他每个人一样,你的任务必须排队等候。

如果浏览器没有自己的ticker,它将使用操作系统的tick cycles。旧版浏览器的最小延迟为10-15ms。HTML5规定,如果延迟时间小于4ms,则应该将其增加到4ms。这被认为是2010年及以后发布的浏览器一致的做法

详见John Resig的《JavaScript计时器如何工作》

编辑:此外,查看Philip Roberts在JSConf EU 2014上的视频《事件循环是什么鬼?》。这对所有接触前端代码的人来说都是必看的。


13

你会想做这个的原因有几个:

  • 有些动作你不想立即运行,但希望在不久的将来某个时间点运行。
  • 你想让之前注册了 setTimeoutsetInterval 的其他处理程序运行。

7

当你希望执行完前一个代码块之前就可以运行后面的代码时,你需要将其添加到传递给setTimeout函数的匿名方法中。否则,你的代码将会等待前一个代码块完成才会继续执行。

例子:

function callMe()
{
   for(var i = 0; i < 100000; i++)
     {
       document.title = i;
     }
} 

var x = 10;
setTimeout(callMe, 0);

var el = document.getElementById('test-id');
el.innerHTML = 'Im done before callMe method';

那就是我使用它的原因。

7
除了之前的回答,我想再添加一个有用的场景:用于从try-catch块中“逃脱”。在try-catch块内部的setTimeout-delay将在块外执行,并且任何异常都会在全局范围内传播。
可能最好的示例场景是:在今天的JavaScript中,由于异步回调的常见使用,您实际上正在try-catch中运行。Deferreds / Promises将回调函数包装在try-catch中,以便能够检测并将异常作为异步链中的错误传播。对于需要在链中的函数来说,这一切都很好,但迟早你会完成所有操作(即获取所有ajax)并希望运行简单的非异步代码,在那里你不希望异常再被“隐藏”。
据我所知,Dojo、Kris Kowal的Q、MochiKit和Google Closure lib都使用try-catch包装(但jQuery不是)。
(在一些奇怪的情况下,我也使用这种技术重新启动单例式代码,而不会导致递归。也就是在同一循环中进行拆卸-重启)。

2

为了允许任何先前设置的超时时间执行。


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