Javascript迭代递归函数使用setTimeout计时不准确,node.js也存在该问题吗?
问题现象原因分析
第一个代码运行结果1200-1600ms的原因
你提供的第一份代码如下:
// Initialise the variables to check when the code started let total_time = performance.now(); let recursions = 0; function test() { recursions++; if (recursions === 60) { // Check how long it took to do 60 recursions console.log(`${performance.now() - total_time}ms`); } // Timeout to call it 17ms later setTimeout(() => test(), 17); } test();
- 首先纠正一个误解:这段代码不是递归,是基于
setTimeout的异步链式调用,不存在调用栈堆叠,setTimeout的作用是将下一次test的执行推入宏任务队列,等待当前执行栈清空后再调度执行。 - 定时调度的固有开销:你设置的每次调度间隔是17ms,理想状态下60次调度总耗时是
60*17=1020ms,但实际运行时每次调度都需要等待事件循环处理队列中的其他任务,累积下来会产生额外的开销。 - 嵌套
setTimeout的强制最小延迟限制:根据HTML规范,当setTimeout的嵌套层级超过5层时,浏览器会强制设置最小超时时间为4ms,不过你设置的17ms已经高于这个阈值,所以这部分影响很小。 - 额外的浏览器限制:如果运行代码的标签页处于后台状态,多数浏览器会将
setTimeout的最小调度间隔拉长到1000ms左右来降低功耗,这也是你可能得到1200-1600ms的核心原因之一,同时浏览器为了降低功耗会合并相近的定时器任务,也会增加额外的延迟。
第二个代码输出0ms的原因
你提供的测量单次耗时的代码如下:
let start_time = performance.now(); let total_time = performance.now(); let measure = 0; function test() { start_time = performance.now(); measure++; if (measure === 60) { console.log(`${performance.now() - total_time}ms`); } console.log(`Taken ${performance.now() - start_time}`); } test();
- 你修改后的代码删除了
setTimeout逻辑,test函数只会同步执行1次,永远不会触发measure===60的判断逻辑。 - 函数内从给
start_time赋值到打印差值的代码逻辑非常简单,执行耗时远低于performance.now()的时间分辨率(目前浏览器为了防范侧信道攻击,普遍将performance.now()的精度限制在1ms左右,低于这个精度的差值会返回0),所以最终得到0ms的结果是正常现象。
Node.js环境下的表现
- 第一个代码在Node.js中运行不会有浏览器后台标签的大延迟限制,但Node.js同样存在嵌套
setTimeout的最小延迟限制(嵌套层级超过5层后最小延迟为1ms),再加上事件循环的调度开销,最终总耗时会略高于1020ms,一般在1100ms左右,不会出现1600ms这类过高的结果。 - 第二个代码在Node.js中运行同样会输出0ms,Node.js中
performance.now()的精度同样不足以捕捉到简单同步代码的执行耗时。
内容的提问来源于stack exchange,提问作者Joy Singh
相关产品推荐
相关产品推荐

