settimeout 的回调函数在宏任务队列中延迟执行,具体时机取决于当前调用栈是否为空以及宏任务队列的状态。浏览器或 node.js 环境会在当前同步代码执行完毕后,检查宏任务队列并取出最老的一个任务放入调用栈执行。

如何设置最小延迟时间
通过给 setTimeout 传入第二个参数(毫秒数),可以指定回调函数的最小等待时间。如果当前没有其他高优先级任务阻塞,回调将在大约该时间后执行;但如果此时有其他宏任务正在排队,它必须等到前面的任务全部执行完才会被调度。

console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 0);
console.log('End');
上述代码的输出顺序为:Start、End、Timeout callback。这证明了即使延迟设为 0,回调也会被推入宏任务队列,直到同步代码执行完毕后才运行。
如何处理多个定时器的竞争
当注册了多个 setTimeout 时,它们按照注册的先后顺序进入宏任务队列。执行引擎会依次从队列头部取出任务执行。如果某个回调的执行时间较长,它会阻塞后续所有已排队的定时器,导致它们的实际执行时间晚于设定的延迟。

console.log('Task 1 start');
setTimeout(() => {
console.log('Timer A (10ms)');
}, 10);
setTimeout(() => {
console.log('Timer B (5ms)');
}, 5);
console.log('Task 1 end');
尽管 Timer B 的延迟更短,但由于 Timer A 先注册,所以输出顺序依然是:Task 1 start、Task 1 end、Timer A (10ms)、Timer B (5ms)。这说明宏任务的执行顺序严格遵循先进先出原则,而非按延迟时间排序。
长时间运行的同步代码对延迟的影响
如果在 setTimeout 之前有一段耗时较长的同步计算,所有的定时器都必须等待这段同步代码完全结束后才能开始调度。这种机制确保了 JavaScript 单线程执行的原子性,但也可能导致定时器“迟到”。
console.log('Heavy computation start');
const start = Date.now();
while (Date.now() - start {
console.log('Delayed callback');
}, 0);
在这个例子中,Delayed callback 会在耗时循环结束后立即执行,而不是在 0 毫秒后。这是因为事件循环必须等待调用栈清空,而耗时循环占用了调用栈,使得定时器无法提前介入。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











