setTimout延迟

浏览器最小延迟时间

根据MDN 文档 setTimeout:实际延时比设定值更久的原因:最小延迟时间中所说:

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度)。

HTML Standard规范中也有提到更具体的:

Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.

简单来说,5 层以上的定时器嵌套会导致至少 4ms 的延迟。

为什么有最小时延 4ms

首先 html 规范是:

  1. 如果设置的 timeout 小于 0,则设置为 0
  2. 如果嵌套的层级超过了 5 层,并且 timeout 小于 4ms,则设置 timeout 为 4ms。

不同浏览器的最低时延会不一致,比如 chrome 的最低时延是 1ms。而如果 timer 嵌套层级很多,那么最低时延是 4ms。具体嵌套层级的阈值不同浏览器也不一致,HTML Standard 当中是 >5,chrome 当中是 >=5

各大浏览器厂商没有按规范实现,是因为各有各的基准和权衡

1
2
3
4
5
6
7
8
setTimeout(() => console.log(5), 5)
setTimeout(() => console.log(4), 4)
setTimeout(() => console.log(3), 3)
setTimeout(() => console.log(2), 2)
setTimeout(() => console.log(1), 1)
setTimeout(() => console.log(0), 0)
// 谷歌打印 1 0 2 3 4 5
// 火狐 0 1 2 3 4 5

为什么不能设置为0:如果浏览器允许 0ms,会导致 JavaScript 引擎过度循环,网站很容易无响应

实现零延时定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(function () {
let timeouts = [];
let messageName = 'zero-timeout-message';

// 保持 setTimeout 的形态,只接受单个函数的参数,延迟始终为 0。
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, '*');
}

function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
let fn = timeouts.shift();
fn();
}
}
}

window.addEventListener('message', handleMessage, true);

// 把 API 添加到 window 对象上
window.setZeroTimeout = setZeroTimeout;
})();

console.log(setZeroTimeout(()=>console.log('0')))

参考文章

如何实现一个零延迟的定时器?

为什么 setTimeout 有最小时延 4ms ?