在 javascript 事件循环中,settimeout 的排序由定时器到期后的插入时机决定:当多个定时器的 delay 均小于或等于当前时间时,它们会按注册顺序依次进入宏任务队列,后注册的排在后面。这意味着如果所有定时器都“立即”触发,它们的执行顺序与代码中的声明顺序完全一致。

不同延迟时间的定时器如何排队
当 setTimeout 设置了不同的 delay 参数时,浏览器或 Node.js 环境会根据到期时间将它们分配到不同的调度槽位。先到期的定时器先被放入宏任务队列,后到期的则等待。即使后注册的定时器延迟更短,它也会比先注册但延迟更长的定时器更早进入队列并优先执行。

console.log('start');
setTimeout(() => {
console.log('timeout 0ms');
}, 0);
setTimeout(() => {
console.log('timeout 50ms');
}, 50);
setTimeout(() => {
console.log('timeout 0ms again');
}, 0);
console.log('end');
上述代码的预期输出为:startendtimeout 0mstimeout 0ms againtimeout 50ms
立即学习“Java免费学习笔记(深入)”;
微任务对宏任务排序的影响
虽然 setTimeout 本身属于宏任务,但其回调的执行时机受到微任务队列的干扰。如果在同一个事件循环阶段中,宏任务触发了微任务(例如通过 Promise.then),这些微任务会在下一个宏任务执行前全部清空。因此,如果两个 setTimeout 同时到期,但其中一个回调中产生了新的微任务,这不会改变它们在宏任务队列中的相对位置,但会影响它们之间的实际执行间隔和上下文切换。

console.log('script start');
setTimeout(() => {
console.log('timeout 1');
Promise.resolve().then(() => console.log('micro 1'));
}, 0);
setTimeout(() => {
console.log('timeout 2');
}, 0);
Promise.resolve().then(() => console.log('micro script'));
上述代码的预期输出为:script startmicro scripttimeout 1micro 1timeout 2
嵌套 setTimeout 的递归排序行为
在 setTimeout 的回调中再次调用 setTimeout 是一种常见模式,用于模拟轮询或动画。这种嵌套调用会导致新的定时器在当前宏任务执行期间被注册,并安排在下一次事件循环中。由于每次注册都会产生一个新的宏任务条目,且新任务总是排在当前任务之后,因此嵌套的 setTimeout 会形成一种链式执行结构,确保任务按严格的先后顺序执行,而不会并发。


















