经常在 JavaScript 库的代码中看到这样的写法:
setTimeout(function() {
...
}, 0);
我想知道为什么要使用这样的包装器代码。
经常在 JavaScript 库的代码中看到这样的写法:
setTimeout(function() {
...
}, 0);
我想知道为什么要使用这样的包装器代码。
非常简单:
浏览器是单线程的,这个单线程(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上的视频《事件循环是什么鬼?》。这对所有接触前端代码的人来说都是必看的。
你会想做这个的原因有几个:
setTimeout
或 setInterval
的其他处理程序运行。当你希望执行完前一个代码块之前就可以运行后面的代码时,你需要将其添加到传递给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';
为了允许任何先前设置的超时时间执行。