超时触发时间可能比预期时间长,原因有很多。本节介绍了最常见的原因。
嵌套超时
如 HTML 标准 中所述,浏览器会在setTimeout被嵌套调用5次后强制执行至少4毫秒的超时。
以下示例可以说明这一点,其中我们将对setTimeout的调用嵌套,延迟时间为0毫秒,并在每次处理程序被调用时记录延迟。前四次,延迟大约为0毫秒,之后大约为4毫秒。
html
| Previous | This | Actual delay |
|---|
jslet last = 0;
let iterations = 10;
function timeout() {
// log the time of this call
logline(new Date().getMilliseconds());
// if we are not finished, schedule the next call
if (iterations-- > 0) {
setTimeout(timeout, 0);
}
}
function run() {
// clear the log
const log = document.querySelector("#log");
while (log.lastElementChild) {
log.removeChild(log.lastElementChild);
}
// initialize iteration count and the starting timestamp
iterations = 10;
last = new Date().getMilliseconds();
// start timer
setTimeout(timeout, 0);
}
function logline(now) {
// log the last timestamp, the new timestamp, and the difference
const tableBody = document.getElementById("log");
const logRow = tableBody.insertRow();
logRow.insertCell().textContent = last;
logRow.insertCell().textContent = now;
logRow.insertCell().textContent = now - last;
last = now;
}
document.querySelector("#run").addEventListener("click", run);
* {
font-family: monospace;
}
th,
td {
padding: 0 10px 0 10px;
text-align: center;
border: 1px solid;
}
table {
border-collapse: collapse;
margin-top: 10px;
}
非活动选项卡中的超时
为了减少后台选项卡的负载(以及相关的电池消耗),浏览器会在非活动选项卡中强制执行最小的超时延迟。如果页面正在使用 Web Audio API AudioContext 播放声音,则也可能会取消此限制。
具体细节取决于浏览器。
Firefox 桌面版和 Chrome 都对非活动选项卡设置了至少1秒的超时。
Firefox for Android 对非活动选项卡设置了至少15分钟的超时,并且可能会完全卸载它们。
如果选项卡包含 AudioContext,则 Firefox 不会限制非活动选项卡。
跟踪脚本的限流
Firefox 对其识别为跟踪脚本的脚本实施了额外的限流。在前台运行时,限流的最小延迟仍然是4毫秒。但在后台选项卡中,限流的最小延迟为10,000毫秒,即10秒,并在文档首次加载30秒后生效。
有关更多详细信息,请参阅 跟踪保护。
延迟超时
如果页面(或操作系统/浏览器)忙于其他任务,超时也可能比预期晚触发。需要注意的一个重要情况是,在调用setTimeout()的线程终止之前,无法执行该函数或代码片段。例如
jsfunction foo() {
console.log("foo has been called");
}
setTimeout(foo, 0);
console.log("After setTimeout");
将写入控制台
After setTimeout
foo has been called
这是因为即使setTimeout被调用时延迟为零,它也会被放入队列中,并在下一个机会执行;而不是立即执行。当前正在执行的代码必须完成才能执行队列中的函数,因此最终的执行顺序可能与预期不符。
页面加载期间的超时延迟
Firefox 会在当前选项卡加载期间延迟触发setTimeout()计时器。触发将延迟到主线程被认为空闲(类似于 window.requestIdleCallback()),或直到加载事件被触发。