nodeJS-eventLoop, 面试必备

eventLoop 是前端面试的常见问题,特别是中/高级前端开发工程师,希望能帮助到你,如有错误还请指点,必更正

浏览器的eventLoop 我们就不说了,之前有一篇文章专门来介绍了浏览器eventLoop

nodeJS的eventLoop, 基于 libuv 库,专注于 I/O 性能(文件、网络、子进程等)。浏览器的核心目标是保证 UI 的流畅响应。nodeJs的事件循环核心目标是高效处理非阻塞 I/O(尤其是文件、网络),

nodeJS的eventLoop 是分阶段性的,不像浏览器中的区分宏任务和微任务的执行

nodeJS的eventLoop中每个阶段中执行的也包含宏任务和微任务

Node.js 事件循环 (基于 libuv)

  • 架构基础: 基于 libuv 库,专注于 I/O 性能(文件、网络、子进程等)。

  • 阶段 (Phases): Node.js 事件循环被明确划分为几个顺序执行 的阶段。每个阶段都有一个先进先出(FIFO)的回调队列。当事件循环进入某个阶段时,它将执行该阶段队列中所有的 回调(直到达到系统相关的限制数量或队列为空),然后才会移动到下一个阶段。注意:process.nextTick() 和微任务队列的执行时机特殊,见下文。

  • 主要阶段 (按顺序):

    1. Timers (定时器阶段): 执行 setTimeout()setInterval() 中到期回调。

    2. Pending Callbacks (待定回调阶段): 执行某些系统操作(如 TCP 错误)的回调。

    3. Idle, Prepare (闲置、准备阶段): 仅 libuv 内部使用。

    4. Poll (轮询阶段 - 核心阶段):

      • 计算阻塞时间: 计算应该阻塞多久以等待新的 I/O 事件(基于下一个定时器的时间)。

      • 处理 I/O 事件: 执行几乎所有 I/O 相关的回调(文件读取、网络请求、用户自定义事件等)。如果轮询队列不为空,执行队列中的所有回调直到队列为空或达到系统限制。如果轮询队列为空:

        • 如果 setImmediate() 调度了回调,则结束 Poll 阶段,进入 Check 阶段执行这些回调。
        • 如果 没有 setImmediate() 回调,则事件循环将在 Poll 阶段阻塞等待新的 I/O 事件到达并立即执行它们的回调。
    5. Check (检查阶段): 执行 setImmediate() 设置的回调。

    6. Close Callbacks (关闭回调阶段): 执行关闭事件的回调(如 socket.on('close', ...))。

  • process.nextTick() 和微任务队列:

    • process.nextTick() 不属于事件循环的任何阶段。 它拥有一个独立的队列。在当前操作(无论事件循环处于哪个阶段)完成后、事件循环继续到下一个阶段之前,Node.js 会清空整个 nextTick 队列。 这意味着 nextTick 的优先级高于 微任务队列。递归调用 nextTick 可能导致 I/O 饥饿(因为事件循环一直被 nextTick 打断,无法进入 Poll 阶段处理 I/O)。
    • 微任务队列 (Microtask Queue): 包含 Promise.then/catch/finallyqueueMicrotask在事件循环的每个阶段(Timers, Pending, Poll, Check, Close)切换之前,Node.js 都会清空整个微任务队列(包括该阶段执行过程中产生的微任务)。 注意:虽然也是在阶段切换前执行,但它的优先级低于 nextTick 队列(先清空 nextTick 队列,再清空微任务队列,然后进入下一阶段)。

3. Node.js 与 浏览器事件循环的最大区别

  • 最大区别:架构目标与阶段划分

    • 浏览器: 核心目标是保证 UI 的流畅响应。它没有明确的阶段划分,主要区分宏任务和微任务,微任务在每次宏任务执行后、渲染前被彻底清空。渲染时机是流程的一部分。
    • Node.js: 核心目标是高效处理非阻塞 I/O(尤其是文件、网络)。它采用明确的、分阶段(Timers -> Pending -> Poll -> Check -> Close)的模型 。每个阶段处理特定类型的 I/O 回调。微任务 (Promise) 和 process.nextTick 的执行时机被严格定义在每个阶段切换的间隙,且 nextTick 优先级高于微任务。 Node.js 没有内置的 UI 渲染概念。
  • 其他关键区别:

    • setImmediate vs setTimeout(..., 0)

      • 在浏览器中,setTimeout(..., 0) 是常见的"尽快"执行方式(但仍是宏任务)。setImmediate 非标准。

      • 在 Node.js 中:

        • setImmediate() 设计在 Check 阶段执行。
        • setTimeout(..., 0)Timers 阶段执行。
        • 主模块/I/O 回调 中调用时,它们的执行顺序是不确定的(受进程性能影响)。
        • 同一个事件循环阶段(如 Poll 阶段)内 调用时,setImmediate 总是先于 setTimeout(..., 0) 执行(因为 Check 在 Timers 之后)。
    • process.nextTick 这是 Node.js 特有的机制,优先级极高(在当前操作后立即执行,甚至在微任务之前),浏览器中没有直接对应物。queueMicrotask 更接近浏览器的微任务行为。

    • I/O 类型: Node.js 需要处理更底层的、多样的 I/O(文件系统、网络套接字、子进程),而浏览器主要处理 DOM、网络请求(fetch/XMLHttpRequest)、用户交互事件等基于 Web API 的 I/O。

    • 渲染: 浏览器事件循环天然包含 UI 渲染步骤。Node.js 事件循环与此无关。

区别:

浏览器事件循环围绕宏任务/微任务划分和UI渲染优化,微任务在宏任务后立即全部执行。Node.js事件循环围绕libuv的明确I/O处理阶段(Timers, Poll, Check等)优化,微任务和nextTick在阶段切换间隙执行,且nextTick优先级最高。目标不同导致结构差异巨大:浏览器保UI,Node保I/O。

我之前一直有一个疑问?

在 同一个事件循环阶段(如 Poll 阶段)内 调用时,setImmediate 总是先于 setTimeout(..., 0) 执行(因为 Check 在 Timers 之后)。check在timers之后,不应该setImmediate在setTimeout后面吗,怎么是先于呢

原因在于,同一个事件循环阶段,进入timer阶段,执行的是上一次的宏任务setTimeout, 本次事件循环在poll阶段产生的宏任务,setTimeout或者setImmediate, 分别在本次的check阶段和下一次的事件循环的timer阶段执行,所以 setImmediate 比setTimeout 先执行,不知这么理解是否正确

相关推荐
卑微前端在线挨打3 分钟前
2025数字马力一面面经(社)
前端
OpenTiny社区18 分钟前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录39 分钟前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊1 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript
bysking1 小时前
【28 - 记住上一个页面tab】实现一个记住用户上次点击的tab,上次搜索过的数据 bysking
前端·javascript
Dream耀1 小时前
跨域问题解析:从同源策略到JSONP与CORS
前端·javascript
前端布鲁伊1 小时前
【前端高频面试题】面试官: localhost 和 127.0.0.1有什么区别
前端
HANK1 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js
極光未晚1 小时前
Vue 前端高效分包指南:从 “卡成 PPT” 到 “丝滑如德芙” 的蜕变
前端·vue.js·性能优化
郝亚军1 小时前
炫酷圆形按钮调色器
前端·javascript·css