面试题答案
一键面试事件循环主要阶段
- timers:这个阶段执行
setTimeout
和setInterval
预定的回调函数。它会检查是否有到期的定时器,如果有,就将对应的回调函数放入队列中执行。 - pending callbacks:执行系统级回调,例如TCP连接错误的回调等。这些回调在系统底层执行完成后,被放入这个队列等待执行。
- idle, prepare:仅Node.js内部使用,一般开发者无需关注。
- poll:这是事件循环的核心阶段。在此阶段,事件循环会查看是否有新的I/O事件,如果有则执行其回调;如果没有新事件且有已到期的定时器,事件循环将移至
timers
阶段执行定时器回调;如果没有新事件且没有到期定时器,事件循环会在此阶段等待新事件。 - check:执行
setImmediate
的回调函数。setImmediate
是在poll
阶段空闲时被触发的。 - close callbacks:执行一些关闭的回调函数,比如
socket.on('close', ...)
。
执行顺序
- process.nextTick:
process.nextTick
的回调函数会在当前执行栈末尾,下一次事件循环之前执行。它会优先于其他所有阶段的任务,包括setTimeout
、Promise
、setImmediate
等。只要当前执行栈执行完毕,就会立即执行nextTickQueue
中的所有回调。 - Promise:
Promise
的then
回调会在本轮事件循环的微任务队列中执行。微任务队列在每个宏任务(如setTimeout
回调、setImmediate
回调等)执行结束后,下一个宏任务开始前执行。例如,当timers
阶段的setTimeout
回调执行完毕后,会先清空微任务队列(如果有Promise
的then
回调在微任务队列中),再进入下一个阶段。 - setTimeout:
setTimeout
的回调会在timers
阶段执行,它按照定时器设定的时间到期后,被放入timers
队列等待执行。但由于事件循环机制,实际执行时间可能会晚于设定时间,因为要等待当前执行栈清空以及其他阶段的执行。 - setImmediate:
setImmediate
的回调在check
阶段执行,它在poll
阶段空闲时被触发,一般会晚于process.nextTick
和Promise
,但早于下一轮事件循环的timers
阶段。
性能优化
- 合理使用定时器:避免设置过多过小的
setTimeout
,因为定时器到期后需要等待当前执行栈清空才能执行,过多的定时器可能导致任务堆积,影响性能。可以根据业务场景,适当合并或调整定时器的时间间隔。 - 优化微任务:
Promise
的then
回调作为微任务,虽然能在宏任务结束后尽快执行,但如果微任务队列过长,也会阻塞后续宏任务的执行。尽量减少微任务中的复杂计算,确保微任务快速执行完毕。 - 正确使用process.nextTick:
process.nextTick
能让任务在当前执行栈末尾尽快执行,但由于它会阻塞下一次事件循环,所以不要在其中执行耗时操作,以免影响整个事件循环的流转。 - 使用setImmediate:
setImmediate
适用于需要在poll
阶段空闲时执行的任务,比如一些不需要立即执行但希望在当前事件循环内执行的清理操作等。合理使用它可以避免阻塞poll
阶段,提高I/O响应性能。