区别两边都是“宏任务 微任务”但 Node 把宏任务拆成了6 个阶段按固定顺序跑微任务不在每个宏任务后清而是在阶段切换间隙清空另外 Node 多了process.nextTick优先级比 Promise 微任务还高。细节版维度浏览器Node.js宏任务来源setTimeout/setInterval/MessageChannel/requestAnimationFrame分6 阶段timerssetInterval,setTimeout → pending callbacks → idle/prepare →poll​ → checksetImmediate → close callbacks微任务时机每个宏任务结束后立刻清空每个阶段之间清空微任务poll 空闲才去 check 跑setImmediate最高优先级无微任务已是最前process.nextTick队列先于所有微任务​setTimeoutvssetImmediate无setImmediatepoll 之后执行setImmediatesetTimeout(fn,0)在 timers 阶段跑 →I/O 回调里永远setImmediate先​渲染每轮末尾有 rAF → 布局 → 绘制无 DOM 渲染区别1.基于环境Node.js事件循环是 Node.js 运行时的一部分它负责管理异步操作例如文件 I/O、网络请求等。浏览器事件循环是浏览器引擎的一部分用于处理诸如 DOM 事件、Ajax 请求等浏览器特定的异步操作。2.事件循环实现Node.jsNode.js 使用 libuv 库来实现事件循环。libuv 是一个跨平台的异步 I/O 库它提供了事件循环以及线程池等功能用于管理异步操作。浏览器浏览器中的事件循环由浏览器引擎实现通常是由 BlinkChrome 和 Opera 使用的引擎、GeckoFirefox 使用的引擎或 WebKitSafari 使用的引擎等实现。3.事件队列Node.jsNode.js 有多个事件队列其中包括 timers 队列setTimeout 和 setInterval 回调、I/O 队列处理文件 I/O、网络 I/O 等、微任务队列process.nextTick 和 Promise以及 setImmediate 队列在每个事件循环迭代结束时执行。浏览器浏览器也有多个事件队列包括宏任务队列例如 DOM 事件、Ajax 请求等、微任务队列Promise 和 MutationObserver 回调、requestAnimationFrame 队列等。4.主线程阻塞Node.jsNode.js 是单线程的当执行 CPU 密集型操作或阻塞操作时会导致事件循环被阻塞从而影响其他异步操作的执行。浏览器浏览器是多线程的JavaScript 代码运行在主线程上但浏览器还有其他线程如渲染线程、网络线程等。主线程阻塞可能会导致页面渲染被延迟。队列归属清空时机process.nextTick队列Node 特有V8 不认识每个原生调用栈返回后立刻清空先于微任务Promise.then/queueMicrotaskJS 语言标准微任务​Node 和浏览器一致在nextTick之后、回到事件循环前清空setImmediateNode 特有宏任务check 阶段执行setTimeout宏任务timers 阶段严格说process.nextTick既不是 JS 标准微任务也不是宏任务——它是 Node自己实现的一个独立队列优先级比 Promise 微任务还高在当前操作完成后、事件循环继续往下走之前就清空。标准里浏览器 / Web 规范根本没有 nextTick只有 Promise / queueMicrotask 才算“微任务”。javaScript事件循环机制一、是什么用事件循环是用来实现单线程非阻塞的因为js是单线程语言。JS中所有的任务都可以分为同步任务立即执行的任务同步任务一般会直接进入到主线程中执行异步任务异步执行的任务例如ajax请求或者setTimeout函数​执行同步代码调用栈中的代码比如 console.log​​​​当调用栈清空后执行当前所有已排队的微任务如 Promise.then、queueMicrotask、MutationObserver​​​​微任务会一次性全部执行完直到微任务队列为空​​​​然后浏览器或 Node.js才会从宏任务队列中取出一个宏任务如 setTimeout、setInterval、I/O、UI 渲染等来执行​​​​执行完一个宏任务后再次检查是否有微任务如果有继续清空微任务队列​​​​如此循环这就是 “事件循环”​​宏任务与微任务✅ 1. ​​宏任务MacroTask / Task​​​​宏任务是 JS 异步任务的一种它的回调会被放入“宏任务队列”中由事件循环在每一轮循环中按顺序取一个执行。​​✅ 常见的宏任务包括宏任务说明setTimeout定时器回调进入宏任务队列setInterval定时器重复执行setImmediateNode.jsNode 环境的“立刻”宏任务I/O 操作如文件读写、网络请求如fetch、fs.readFile在浏览器中一般是 Promise 化的UI 渲染浏览器比如重绘、回流整体 script 代码一个script标签内的同步代码也视为一个宏任务回调函数只是一种函数传递模式本身不分同步/异步、宏任务/微任务。异步回调的分类属于setTimeout、用户交互、I/O等宏任务源的回调 → 宏任务。属于Promise.then、queueMicrotask等微任务源的回调 → 微任务。关键区分看回调函数被哪个异步任务包装而非回调函数本身。✅ 2. ​​微任务MicroTask / Jobs​​​​微任务也是异步任务但它的优先级比宏任务高会在当前宏任务执行完成后、下一个宏任务开始之前清空整个微任务队列。​​✅ 常见的微任务包括微任务说明Promise.then()/Promise.catch()/Promise.finally()Promise 的回调是微任务MutationObserver浏览器DOM 变化监听微任务queueMicrotask(fn)显式插入一个微任务process.nextTickNode.js优先级比微任务还高Node 环境特有执行宏任务时遇到微任务就放入微任务任务队列中当宏任务执行完成后会查看微任务的事件队列然后将微任务依次执行完再进行循环执行下一个宏任务。注意await console.log(3); // ⚠️ 重点await 的左侧是同步的console.log(4); // ⚠️ 这行会被“推迟”为微任务await async2(); ⚠️ 重点await 会让后面的代码变成“微任务”console.log(async1 end); //变成一个微任务了详见下面的输出题。nodeTimers用于存储定时器的回调函数setInterval,setTimeoutPending callbacks执行与操作系统相关的回调函数比如启动服务器端应用时监听端口操作的回调函数就在这里调用。闲置阶段(idle, prepare)仅系统内部使用轮询阶段(poll)检索新的 I/O 事件;执行与 I/O 相关的回调几乎所有情况下除了关闭的回调函数那些由计时器和 setImmediate() 调度的之外其余情况 node 将在适当的时候在此阻塞检查阶段(check)setImmediate() 回调函数在这里执行关闭事件回调阶段(close callback)一些关闭的回调函数例如关闭数据库连接的回调为什么netick不算“微任务”微任务是ECMAScript 规范概念Job Queue浏览器和 Node 都有只有 Promise / queueMicrotask 是。process.nextTick是Nodelibuv process binding​ 加的钩子浏览器压根没有IE 那个setImmediate是另一回事。Node 官方文档自己都写nextTick queue is not part of the Event Loop——它跑在事件循环每一阶段切换之前所以能饿死 I/O。追问防御“那queueMicrotask和 Promise.then 有区别吗”→ 优先级一样都在 nextTick 之后区别是queueMicrotask不会被 thenable 链式吞掉更纯粹Node 11 / 浏览器通用。“nextTick 和 setImmediate 谁快”→ 同一次调用里nextTick永远先它在阶段内就清完了setImmediate要等事件循环走到check 阶段哪怕只差一个阶段也是下一拍。“浏览器有等价物吗”→ 没有严格等价。queueMicrotask最接近微任务语义以前有人用MessageChannel模拟 nextTick 的“早于 setTimeout”行为但现在直接queueMicrotask就行。⚠️ 红线❌ 别说“nextTick 是 Node 的微任务”——面试官就等你踩这个坑。正确说法是“Node 独立的高优先级队列比微任务还早不属于规范定义的微任务”。✅ 加分收尾“所以 Node 里实际是三层nextTick → 微任务(Promise) → 宏任务阶段不是大家直觉的两层。” 继续。为什么 async/await 是微任务await console.log(3); // ⚠️ 重点await 的左侧是同步的console.log(4); // ⚠️ 这行会被“推迟”为微任务打印34​​因为 JavaScript 的await语法规定await的左侧表达式例如console.log(3)或一个 Promise是 ​​同步求值的​​它会在暂停异步操作之前先执行并得到结果而真正“暂停后续代码、等待 Promise 完成”的动作才会引入异步微任务。​​1.与 Promise 密切相关async/await 是基于 Promise 实现的await 关键字等待的是一个 Promise 对象的状态。由于 Promise 的 then 方法注册的回调函数属于微任务因此 await 后面的代码也会作为微任务来执行。2.顺序执行async/await 语法让异步代码看起来像同步代码一样顺序执行因此await 后面的代码应该在当前任务执行结束后立即执行以保持程序的顺序执行语义。3.处理异步结果await 后面的代码通常是在等待异步操作的结果并且需要在 Promise 解决后立即执行以便处理异步操作的结果。async/await底层https://blog.csdn.net/wyzqhhhh/article/details/136002372?sharetypeblogdetailsharerId136002372sharereferPCsharesourcewyzqhhhhspm1011.2480.3001.8118为什么先微后宏1.可以确保微任务在下一个宏任务之前执行提高这种响应性能。这样Promise的回调、MutationObserver等微任务能够在当前任务结束后立即执行不必等待下一个宏任务。这对于一些需要及时响应的场景例如Promise的resolve/reject、用户交互等非常重要避免了不必要的延迟。2.而且微任务通常代表着 “本次执行流程中产生的后续逻辑”比如Promise 的.then()是在某个异步操作完成后要立刻执行的回调它们往往依赖于当前执行上下文的数据状态逻辑上应该尽快执行如果先执行宏任务有可能导致微任务中的逻辑被延后造成数据不一致或逻辑断裂3.避免视觉或逻辑上的不一致尤其是 UI 相关在浏览器中微任务通常在渲染之前执行如果微任务如状态更新、Promise 回调被延迟到宏任务之后可能导致数据更新了但 UI 没及时渲染用户看到 “卡顿” 或 “状态不符合预期”先执行微任务可以保证页面状态、数据变更先处理完再考虑渲染或其它宏任务如用户交互输出顺序问题1. 用多个setTimeout解释事件循环console.log(1); // 同步 setTimeout(() { console.log(2); // 宏任务 }, 0); Promise.resolve().then(() { console.log(3); // 微任务 }); setTimeout(() { console.log(4); // 宏任务 }, 0); console.log(5); // 同步 15324第 1 步执行主线程整体 script是一个宏任务遇到console.log(1)➜ ​​输出 1​​遇到setTimeout(..., 0)把回调放入 ​​宏任务队列​​遇到Promise.resolve().then(...)把回调放入 ​​微任务队列​​遇到setTimeout(..., 0)​​又一个 setTimeout回调也放入宏任务队列​​遇到console.log(5)➜ ​​输出 5​​✅ ​​此时主线程第一个宏任务执行完毕​​​​调用栈为空事件循环开始工作​​第 2 步检查微任务队列先执行所有微任务微任务队列里有() { console.log(3) }执行它 ➜ ​​输出 3​​✅ ​​微任务队列清空​​第 3 步开始下一轮事件循环执行第一个宏任务setTimeout宏任务队列中第一个是() { console.log(2) }执行它 ➜ ​​输出 2​​第 4 步检查是否有微任务本轮没有新的微任务没有继续下一轮事件循环第 5 步执行下一个宏任务第二个 setTimeout宏任务队列中下一个是() { console.log(4) }执行它 ➜ ​​输出 4​​console.log(1); // 同步 setTimeout(() { console.log(2); // 宏任务 }, 1000); Promise.resolve().then(() { console.log(3); // 微任务 }); setTimeout(() { console.log(4); // 宏任务 }, 0); console.log(5); // 同步 15342“JavaScript 通过事件循环机制处理同步与异步任务。同步代码如console.log(1)和console.log(5)会立即执行Promise.then属于微任务在当前主线程执行完后立即执行输出 3而setTimeout是宏任务即使延迟为 0 也会放入任务队列等待下一轮事件循环执行所以延迟 0 的setTimeout输出 4 先于延迟 1000ms 的setTimeout输出 2。最终顺序为1 → 5 → 3 → 4 → 2。”2.输出顺序问题1async function async1 () { console.log(async1 start); await async2(); ⚠️ 重点await 会让后面的代码变成“微任务” console.log(async1 end); //变成一个微任务了 } async function async2 () { console.log(async2) }。//async2执行完事之后 看后面的同步任务。后面的同步任务走完了 再去看await后面那一条微任务 console.log(script start) setTimeout(function() { console.log(setTimeout) }, 0); async1(); new Promise(function (r) { console.log(promise1); r(); }).then(function() { console.log(promise2) }); console.log(script end); script start async1 start async2 promise script end async1 end promise2 setTimeout2function async2() {console.log(async2);return Promise.resolve(); // 返回一个 resolved 的 Promise}function async1() {console.log(async1 start);return async2().then(() {console.log(async1 end); // 这是微任务});}async1();打印async1 startasync2async1 end3console.log(1); async function async1() { console.log(2); await console.log(3); // ⚠️ 重点await 的左侧是同步的 console.log(4); // ⚠️ 这行会被“推迟”为微任务 } setTimeout(function () { console.log(5) }) async1() new Promise(function (resolve) { console.log(6); resolve() }).then(function () { console.log(7) }) console.log(8) 12368475​​因为 JavaScript 的await语法规定await的左侧表达式例如console.log(3)或一个 Promise是 ​​同步求值的​​它会在暂停异步操作之前先执行并得到结果而真正“暂停后续代码、等待 Promise 完成”的动作才会引入异步微任务。​​4执行 console.log(7. setTimeout 2);→ 输出 7. setTimeout 2 然后执行Promise.resolve().then(...)→ ​​这个是一个微任务​​ ⚠️ ​​但注意这个 Promise.then 是在宏任务内部创建的它不会立即执行而是被放入当前宏任务执行完后的微任务队列中​​ ​​当前宏任务即 setTimeout 的回调执行完毕​​ ​​事件循环机制会清空当前微任务队列​​ 这时候刚刚那个 Promise.resolve().then(...)里的回调会被执行 console.log(1. 开始); setTimeout(() { console.log(2. setTimeout); }, 0); Promise.resolve().then(() { console.log(3. Promise 1); }); console.log(4. 中间); Promise.resolve().then(() { console.log(5. Promise 2); Promise.resolve().then(() { console.log(6. 内层 Promise); }); }); setTimeout(() { console.log(7. setTimeout 2); Promise.resolve().then(() { console.log(8. setTimeout 中的 Promise); }); }, 0); console.log(9. 结束); 149356278​执行同步代码调用栈中的代码比如 console.log​​​​当调用栈清空后执行当前所有已排队的微任务如 Promise.then、queueMicrotask、MutationObserver​​​​微任务会一次性全部执行完直到微任务队列为空​​​​然后浏览器或 Node.js才会从宏任务队列中取出一个宏任务如 setTimeout、setInterval、I/O、UI 渲染等来执行​​​​执行完一个宏任务后再次检查是否有微任务如果有继续清空微任务队列​​​​如此循环这就是 “事件循环”​​6task1() // 同步任务执行 500ms const timer1 setTimeout(cb1,100)// cb1 是宏任务回调 const timer2 setTimeout(cb2, 200) const timer3 setTimeout(cb3,600) // 在task1开始的第400ms用户发起点击对应clickCb const clickcb function () { // 点击事件回调执行时长200ms 点击事件的回调是宏任务用户交互属于宏任务 } ✅ 关键结论 点击事件的触发不是“立即执行回调”而是“将回调加入队列等待主线程调度”。 能否立即执行取决于主线程是否空闲若主线程被长任务如 task1占用点击回调会被延迟执行。 实际开发中的优化避免长任务阻塞主线程如拆分任务、用 Web Worker以提升交互响应速度。 • 定时器延迟是“相对主线程空闲时间”即使定时器设置了延迟若主线程被长任务阻塞回调会延迟到主线程空闲后才执行。 • 用户事件会被“延迟执行”若主线程被占用用户事件的回调会排队等待直到主线程空闲。 • 定时器入队时间决定了执行顺序先入队的定时器如 timer1会优先执行即使它的延迟时间更短。 ✅ 最终执行时间表相对 task1 启动时刻 回调函数 入队时间 实际执行开始时间 执行耗时 说明 timer1 100ms 500ms 0ms 主线程空闲后立即执行 timer2 200ms 600ms 0ms 主线程空闲后立即执行 clickCb 400ms 700ms 200ms 被 task1 阻塞后执行 timer3 600ms 1100ms 0ms 入队时间600ms早于执行时间 ✅ 时间线推演单位毫秒从 task1 开始计时 1. task1 执行持续 500ms • 时间段0ms ~ 500ms • 行为 • 主线程被 task1 占用无法执行其他任务如定时器回调、点击事件。 • 定时器 timer1100ms、timer2200ms、timer3600ms的计时器已启动但回调不会立即执行需等待主线程空闲。 2. 用户点击事件触发第 400ms • 触发时刻400mstask1 执行到第 400ms 时 • 行为 • 点击事件的回调 clickCb执行时长 200ms会被立即加入宏任务队列但由于主线程正忙于 task1需等待 task1 完成后才执行。 3. task1 完成500ms 时刻 • 完成时刻500ms • 行为 • 主线程释放开始处理已入队的宏任务按入队顺序执行。 • 此时宏任务队列中的任务按入队时间排序 1. timer1 回调入队时间100ms 2. timer2 回调入队时间200ms 3. clickCb入队时间400ms 4. timer3 回调入队时间600ms暂未触发 4. 宏任务队列执行顺序 时间段 执行任务 任务入队时间 任务执行耗时 实际执行开始时间 500ms~600ms timer1回调 100ms 立即完成 500ms 600ms~700ms timer2 回调 200ms 立即完成 600ms 700ms~900ms clickCb 400ms 200ms 700ms 1100ms~1200ms timer3 回调 600ms 立即完成 1100ms7setTimeout(function () { console.log(1) }) new Promise(function (resolve) { console.log(2); resolve(); console.log(3); }).then(function () { console.log(4) }) //2341 // ​​resolve()是 Promise 构造函数中用来将 Promise 的状态从 ​​pending等待中​​ 变为 ​​fulfilled已兑现​​ 的方法。只有调用了 resolve()或 reject().then()或 .catch()中的回调才会被放入任务队列并在适当的时候执行。如果不调用 resolve()Promise 会一直处于 pending 状态.then()回调永远不会执行。​​