页面上setTimeout的执行间隔变化

页面上通过JavaScript执行setTimeout时,是否会按照 timeout 设置的时间执行?

大家都知道,JavaScript中的 setTimeout 方法可以延迟执行。

以前面试时还会问,setTimeout 延迟的时间是否跟设置的 timeout 一致。现在大家都知道,timeout会不一致,需要结合微任务和宏任务的繁忙情况而定。

完结,撒花~


然而并没有,那么,接下来说一些可能平时不太关注的情况。

先实现一个时间转换的函数,方便将时间转换为更易阅读的格式

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
28
29
30
31
32
33
34
35
36
37
38
function transform(date = new Date(), fmt = 'yyyy-MM-dd HH:mm:ss SSS') {
if (date == null) return '';
if (!(date instanceof Date)) date = new Date(date);
if (isNaN(date.getTime())) return '';

const pad = (v, len = 2) => String(v).padStart(len, '0');

const hours24 = date.getHours();
const hours12 = hours24 % 12 || 12;

const weekMap = ['日','一','二','三','四','五','六'];

const map = {
yyyy: date.getFullYear(),
yy: String(date.getFullYear()).slice(-2),
MM: pad(date.getMonth() + 1),
M: date.getMonth() + 1,
dd: pad(date.getDate()),
d: date.getDate(),
HH: pad(hours24),
H: hours24,
hh: pad(hours12),
h: hours12,
mm: pad(date.getMinutes()),
m: date.getMinutes(),
ss: pad(date.getSeconds()),
s: date.getSeconds(),
SSS: pad(date.getMilliseconds(), 3),
q: Math.floor((date.getMonth() + 3) / 3),
E: weekMap[date.getDay()]
};

const tokens = Object.keys(map).sort((a, b) => b.length - a.length);
for (const t of tokens) {
fmt = fmt.replace(new RegExp(t, 'g'), map[t]);
}
return fmt;
}

以上代码可在打开浏览器控制台后,先在控制台执行一次

情况一

嵌套超时的情况

1
2
3
4
5
6
7
8
9
10
let iterations = 10

function timeoutFn() {
console.log(Date.now())
if(iterations-- > 0) {
setTimeout(timeoutFn, 0)
}
}

setTimeout(timeoutFn, 0);

这里将 timeout 的间隔设置为 0,用于说明这个特性

打开空白页面(防止页面中有功能代码阻塞脚本执行造成影响),输入以下代码并按回车键后执行

发现没有,前4次,间隔都是预期的 0ms 时间,但是往后,间隔则在 4ms 左右。

原因是 HTML标准 说明的 If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.

情况二

页面/标签 可见性差异

1
2
3
4
5
6
7
8
9
10
11
12
function timeoutFn(count = 0, timeout = 500) {
setTimeout(()=>{
console.log(transform(), count)

timeoutFn(count + 1, timeout)
}, 500)
}

document.addEventListener('visibilitychange', () => {
console.log('visibilitychange changed: ', document.hidden ? 'hidden' : 'visible')
});
timeoutFn(0, 500)

执行后,可以看到间隔基本是在 500ms 的间隔,这个是符合预期的。

尝试切换到其他标签页,然后等一会后(间隔3-5秒后,方便观察),重新激活原标签页,观察控制台输出。

visibilitychange变化时的控制台输出

在页面不可见时,间隔变为了 1 秒,当标签页/页面重新激活(可见时),间隔时间会恢复为设置的 timeout 值

此种调整旨在节约CPU资源和电池消耗,官方称为 普通节流 或者 最小节流

在MDN文档中有描述,具体查看 延时比指定值更长的原因

标签页置于后台时,定时器间隔调整为1秒是标准行为(注意若存在有声音频播放时,节流会被豁免)

你以为到这里就结束了?NO NO NO~ 看看情况三

情况三

当标签页置于不可见超过阈值时(测试的情况是1分钟,有待确定),会启用 强制节流 或者叫 加强节流 的模式

注,此模式在Chrome浏览器下可验证复现,其他浏览器待验证

通过控制台输出可以看到,在19:03:32 开始页面不可见,19:04:31 之后,间隔时间再次变化

visibilitychange变更为不可见时

在页面不可见超过了 1分钟 后,setTimeout的执行间隔也变更为了 1分钟(截图中 ,19:04:31 后间隔变更)

注意 文档 从 Chrome 88 开始,系统会对链接的 JS 计时器施加严格的节流限制 中描述是5分钟,测试是1分钟,可能与版本有关(待确认具体规则后更新)

在Android系统下,加强节流 模式会更激进,间隔会被调整为15分钟,为了更大程度的节约资源(减少对CPU和电池的消耗)

其他补充

在页面播放音频时(播放有声音频的网页会被视为可见于用户,并会被豁免于后台计时器节流。 静默音频流不会授予豁免权),普通节流和强化节流通常都会被豁免,不会触发。这意味着即使标签页在后台,计时器(setTimeout / setInterval)仍然会以接近正常的频率运行,以确保音频能够持续、流畅地播放。

参考文档:

作者

Larify

发布于

2025-10-23

更新于

2025-10-23

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×