settimeout回调属于宏任务,由运行时在延迟到期后将其作为新宏任务插入队列末尾,需等待当前执行栈清空及所有微任务执行完毕后才执行。

setTimeout 的回调函数本身不会“进入”宏任务队列,而是由浏览器或 Node.js 运行时在指定延迟后,**将该回调函数作为一项新的宏任务插入到宏任务队列末尾**。
定时器触发的本质:不是立即入队,而是延迟调度
调用 setTimeout 时,JS 引擎会启动一个底层计时器(如系统 timer),但此时回调函数并未放入任何任务队列。它只是被注册、挂起,等待时间到期。
当设定的延迟时间到达(比如 100ms),运行时会检查当前是否处于 JS 执行栈空闲状态:
- 如果主线程空闲(执行栈为空),运行时立刻把该回调作为宏任务推入宏任务队列,并在下一轮事件循环中执行;
- 如果主线程正忙(比如正在执行一个耗时 500ms 的同步函数),即使 100ms 到了,回调也不会立即执行,而是等到当前同步代码执行完、执行栈清空后,才被推入宏任务队列——所以实际执行时间 ≥ 设定延迟。
它属于宏任务,不是微任务
JavaScript 规范明确将 setTimeout(以及 setInterval、setImmediate、I/O 回调等)归类为宏任务源(macrotask source)。与之相对,Promise.then、MutationObserver、queueMicrotask 属于微任务(microtask)。
这意味着:
- setTimeout 回调总是在当前宏任务执行完 + 所有微任务清空之后,才从宏任务队列中取出执行;
- 即使设为 0ms(
setTimeout(fn, 0)),它也必须等完当前宏任务和所有已排队的微任务,才能执行——所以它比 Promise.then 晚。
一次 setTimeout 调用对应一次宏任务入队
每次调用 setTimeout,都独立触发一次“延迟结束后入队”的行为。例如:
console.log(1); setTimeout(() => console.log(2), 0); // 入队一个宏任务 Promise.resolve().then(() => console.log(3)); // 入队一个微任务 console.log(4); // 输出顺序:1 → 4 → 3 → 2
这里 console.log(2) 是作为单独的宏任务,在第一轮事件循环的微任务(3)执行完后,才从宏任务队列中取出执行。
注意:Node.js 中的差异(可选了解)
在 Node.js 中,setTimeout 属于 timer 阶段,是事件循环六个阶段之一。它仍属宏任务,但入队时机受事件循环阶段控制:只有当执行到 timer 阶段且有到期定时器时,才批量将回调推入宏任务队列并执行。这也解释了为什么在 Node.js 中连续多次 setTimeout(fn, 0) 可能被合并或调度略有不同,但语义上仍是宏任务。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











