浏览器为啥要对 JavaScript 定时器“踩刹车”?

各位开发者朋友,不知道你有没有遇到过这种情况:你写了个 setTimeout(fn, 0),满心期待着它立刻执行,结果它却"磨蹭"了大概 4 毫秒才跑起来?别怀疑,这不是你的代码写错了,而是浏览器在背后悄悄"搞了小动作"!今天咱们就来聊聊,浏览器为啥要对这些定时器"节流"(throttle),以及面对这些限制,我们又能有哪些新选择。

⏰ 1. 定时器并不"准时"

如果你尝试过下面这段代码:

JavaScript 复制代码
const start = performance.now();
setTimeout(() => {
  console.log(performance.now() - start); // 输出大概是4毫秒
}, 0);

你会发现,即便是设置为 0 毫秒延迟的 setTimeout,实际执行也会花费大约 4 毫秒。这是因为浏览器为了避免开发者"滥用"定时器(比如疯狂循环调用 setTimeout),从而设置了最低 4 毫秒的延迟限制。这样一来,可以防止某些网站过度消耗用户的电池电量或者阻塞页面交互。

浏览器的"节流策略"还会因情况而变化

  • 设备使用电池时 :比如旧版的 Edge 浏览器会将延迟提升到 16 毫秒
  • 标签页处于后台时 :Chrome 对后台标签页的定时器延迟甚至可能达到 1 秒!😱

🤔 2. 既然节流,为何还出新 API?

看到这里,你可能会有个疑问:既然 setTimeout 因为被滥用而被限制了,那为什么浏览器们还在不断推出新的定时器 API 呢?比如已经"凉了"的 setImmediate,或是 Promise,又或是新秀 scheduler.postTask()?难道它们最终不会面临同样的"被节流"的命运吗?

这个问题也困扰了原文作者很久。直到他在开发一个纯 JavaScript 实现的 IndexedDB API(fake-indexeddb)时,才重新审视了这个问题。IndexedDB 希望在事件循环中没有未完成的任务时自动提交事务,换句话说,就是在所有微任务(microtasks)完成后,但任何宏任务(比方说 setTimeout 这样的"任务")开始之前。

为了模拟这个过程,fake-indexeddb 在 Node.js 中使用了 setImmediate(这很合适,因为它恰好在微任务之后、其他任务之前执行,且没有延迟限制),在浏览器中则不得不使用 setTimeout。结果呢?作者发现在 Chrome 中某个操作需要 4.8 秒,而在 Node 中仅需 300 毫秒(慢了 16 倍!) .

🧪 3. 2025 年,我们有哪些定时器选择?

既然 setTimeout 在浏览器中表现如此不尽人意,那在 2025 年的今天,我们有没有更好的选择呢?作者测试了一些方案:

浏览器 setTimeout MessageChannel window.postMessage scheduler.postTask
Chrome 139 4.2 ms 0.05 ms 0.03 ms 0.00 ms
Firefox 142 4.72 ms 0.02 ms 0.01 ms 0.01 ms
Safari 18.4 26.73 ms 0.52 ms 0.05 ms 未实现

测试数据为 101 次迭代的中位数(单位:毫秒)

从表格中可以清晰地看到:

  • setTimeout:在各个浏览器中都有明显的延迟(Safari 尤其突出)。
  • MessageChannel.postMessagewindow.postMessage:表现比 setTimeout 好很多,延迟显著降低。
  • scheduler.postTask在 Chrome 和 Firefox 中表现最佳,延迟极低甚至为 0,但目前 Safari 尚未实现。

所以,如果你现在需要高精度的定时器,scheduler.postTask 似乎是当下的最佳选择(当然,要考虑 Safari 的兼容性问题)。

💎 4. 总结与启示

  • 浏览器对 setTimeout 和 setInterval 等定时器的节流 ,主要是出于性能和保护用户电池寿命的考虑,防止糟糕的代码拖垮整个浏览器或设备。
  • 这种节流策略并非一成不变,它会根据设备是否接通电源、标签页是否处于后台等因素动态调整。
  • 对于需要高精度定时对性能敏感的场景(比如实现动画、模拟 IndexedDB 事务等),传统的 setTimeout 可能不再是最佳选择。
  • 可以关注并尝试使用新的 scheduler.postTask API(当然,要准备好回退方案),或者根据实际情况考虑 MessageChannel 等替代方案。
  • 并非所有定时器任务都需要高精度。对于很多日常任务(如简单的延迟、轮询),setTimeout 和 setInterval 仍然完全够用且兼容性最好。

希望这篇译文能帮你理解浏览器定时器背后的"小秘密",以及如何在不同的场景下做出更合适的选择。如果你在项目中遇到过定时器的"坑",或者对新的定时器 API 有使用经验,欢迎在评论区分享交流! 🎉

原文链接:nolanlawson.com/2025/08/31/...

原文作者:Nolan Lawson

相关推荐
m0_616188492 小时前
el-table的隔行变色不影响row-class-name的背景色
前端·javascript·vue.js
zheshiyangyang2 小时前
Vue3组件数据双向绑定
前端·javascript·vue.js
大翻哥哥3 小时前
Python上下文管理器进阶指南:不仅仅是with语句
前端·javascript·python
Monly214 小时前
Vue:下拉框多选影响行高
前端·javascript·vue.js
小桥风满袖4 小时前
极简三分钟ES6 - ES8中对象扩展
前端·javascript
超人不会飛4 小时前
Vue markdown组件 | 流式 | 大模型应用
前端·javascript·github
艾小码5 小时前
Vue模板进阶:这些隐藏技巧让你的开发效率翻倍!
前端·javascript·vue.js
艾小码5 小时前
还在手动加载全部组件?这招让Vue应用性能飙升200%!
前端·javascript·vue.js
三十_5 小时前
【实录】多 SDK 日志乱象的解决方案:统一日志 SDK 设计分享
前端·javascript