餐饮 网站模板网络营销的方法
这是个非常重要的面试 & 实战知识点:浏览器和 Node.js 的事件循环(Event Loop)虽然概念相同,但机制不同。
我来从本质、宏任务/微任务、I/O处理、多线程差异四方面帮你系统讲清楚它们的区别。
✅ 执行流程总结:
1. 先执行所有同步代码(主线程代码)
-
所有你直接写在 JS 文件里的代码会立即执行。
-
包括变量定义、函数调用(只要不是异步)、
require()
等等。
2. 同步代码执行完毕后,进入事件循环(Event Loop)
-
开始一轮又一轮的事件循环。
-
每一轮事件循环按六大阶段运行,每个阶段中处理对应的异步回调。
🔄 所以总结为一句话:
Node.js 先执行主线程中的所有同步任务,再开始事件循环处理异步任务(包括定时器、IO、事件监听等)。
✅ 1. 相同点(基本概念一样)
-
都是单线程执行主任务
-
都使用事件队列 + 执行栈 + 回调机制
-
都支持微任务(microtask)和宏任务(macrotask)
🚀 2. 不同点:事件循环机制差异
特性 | 浏览器 | Node.js |
---|---|---|
执行环境 | JavaScript 引擎(通常是 V8)+ 浏览器 API | JavaScript 引擎 + libuv(底层C库) |
宏任务(macrotask) | setTimeout , setInterval , setImmediate , UI事件等 | setTimeout , setInterval , setImmediate , I/O , close callbacks |
微任务(microtask) | Promise.then , MutationObserver , queueMicrotask | Promise.then , process.nextTick , queueMicrotask |
微任务优先级 | Promise.then 是主流 | process.nextTick > Promise.then |
渲染 | 有 UI 渲染阶段(浏览器每帧约 16ms) | 无 UI 渲染逻辑 |
阶段结构 | 比较简单:宏任务 -> 微任务 -> 渲染 | 更复杂:6个阶段的 Event Loop 阶段 |
🔧 3. Node.js 的六大阶段(基于 libuv)
Node.js 的事件循环分为 6个阶段:
-
timers:处理
setTimeout
和setInterval
-
pending callbacks:处理某些系统操作的回调
-
idle, prepare:内部专用
-
poll:处理 I/O 事件,等待回调
-
check:执行
setImmediate
回调 -
close callbacks:关闭回调,如
socket.on('close', ...)
每个阶段结束后会执行微任务队列(microtask queue),process.nextTick
优先于 Promise 微任务执行。
🧠 4. 举个区别示例
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
Node.js 中输出顺序:
nextTick
Promise
setTimeout
setImmediate // 注意:在文件顶层执行,setTimeout 通常先于 setImmediate
浏览器中输出顺序:
Promise
setTimeout
// setImmediate 是 Node 专有的,在浏览器中不存在
📌 小结
特性 | 浏览器 | Node.js |
---|---|---|
微任务顺序 | Promise 优先 | process.nextTick > Promise |
渲染机制 | 有 UI 渲染 | 无 |
定时器行为 | setTimeout 每次都在下一个宏任务 | setTimeout 在 timers 阶段 |
setImmediate | 不支持 | 专属,check 阶段执行 |
事件循环复杂度 | 简单,宏+微 | 复杂,6阶段 |
需要我画图对比两者的执行流程?或者用一段代码让你调试执行顺序?欢迎继续提问。
🚨 注意事项
-
process.nextTick()
总是优先于所有微任务,即使是Promise
。 -
setImmediate()
和setTimeout(..., 0)
谁先执行取决于调用时的上下文。 -
每个阶段结束后都会检查微任务队列,执行所有微任务后才进入下一个阶段。
二、每一个宏任务执行完毕后,立即执行所有微任务队列(microtask queue)中的任务。
✅ 每一个宏任务执行完毕后,立即执行所有微任务队列(microtask queue)中的任务。
🔁 事件循环中的顺序(浏览器端)
-
从宏任务队列中取出一个宏任务执行(如
setTimeout
、UI 事件
、ajax 回调
等) -
宏任务执行完之后
-
➜ 立即执行所有微任务(如
Promise.then
,queueMicrotask
,MutationObserver
) -
微任务队列清空后,开始下一轮事件循环(Event Loop)
📌 举例说明
console.log('script start');setTimeout(() => {console.log('setTimeout');
}, 0);Promise.resolve().then(() => {console.log('promise1');
}).then(() => {console.log('promise2');
});console.log('script end');
浏览器中的输出:
script start
script end
promise1
promise2
setTimeout
✅
script
是第一个宏任务,执行完后进入微任务队列,再继续下一个宏任务(setTimeout
)。
✅ 结论总结
-
每轮事件循环(tick)中:
-
先执行一个宏任务
-
再执行所有微任务
-
然后开始下一轮宏任务
-
-
所以微任务的优先级比宏任务高
-
微任务不会“等所有宏任务执行完才开始”,而是“每个宏任务结束之后马上执行”
三、事件循环执行顺序代码练习
首先下面两行代码是无法再浏览器中执行的,只能在nodejs环境里执行
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
现在我来给你一段能在两个环境中都运行,但执行顺序不同的代码,并详细解释其执行差异。
✅ 通用代码(两个环境都能执行):
setTimeout(() => {console.log('timeout');
}, 0);Promise.resolve().then(() => {console.log('promise');
});queueMicrotask(() => {console.log('microtask');
});
🟢 Node.js 输出顺序:
microtask
promise
timeout
🟡 浏览器输出顺序(Chrome, Firefox, Safari 等):
microtask
promise
timeout
❗看起来一样?别急。
我们来用更复杂一点的例子,展示 Node.js 和浏览器真实存在的执行差异:
🌟 关键对比示例代码:
setTimeout(() => {console.log('setTimeout');
}, 0);Promise.resolve().then(() => {console.log('Promise 1');
});Promise.resolve().then(() => {console.log('Promise 2');
});console.log('sync');
🟢 Node.js 输出顺序:
sync
Promise 1
Promise 2
setTimeout
🟡 浏览器输出顺序:
sync
Promise 1
Promise 2
setTimeout
🤔 看似又一样?
真正输出顺序不同的代码如下:
Promise.resolve().then(() => {console.log('Promise');
});setTimeout(() => {console.log('timeout');
}, 0);setInterval(() => {console.log('interval');process.exit(); // Node 专用
}, 0);
-
在 Node.js 中,
setInterval
的第一次执行可能比setTimeout
慢,取决于事件循环 timing。 -
而在 浏览器中,
setInterval
和setTimeout
都在宏任务中,但执行间隔由定时器精度控制(最小为 4ms)。
但你要求的是更典型的:
🔥 终极演示(顺序不同的通用代码):
setTimeout(() => console.log('timeout'), 0);Promise.resolve().then(() => console.log('promise'));console.log('sync');
✅ 浏览器输出:
sync
promise
timeout
✅ Node.js 输出(一样):
sync
promise
timeout
🧠 那到底如何展示 顺序不同?
我们需要 Node 专有函数 setImmediate
来区别:
🧪 真正有区别的对比代码(只能在 Node.js 和浏览器区分):
setTimeout(() => console.log('setTimeout'), 0);setImmediate?.(() => console.log('setImmediate'));Promise.resolve().then(() => console.log('Promise'));queueMicrotask(() => console.log('Microtask'));console.log('sync');
✅ Node.js 输出(常见):
sync
Microtask
Promise
setTimeout
setImmediate
✅ 浏览器输出:
(浏览器没有 setImmediate
,所以会报错或忽略)
sync
Microtask
Promise
setTimeout
📌 总结
操作类型 | Node.js 优先级 | 浏览器优先级 |
---|---|---|
console.log | 同步任务 | 同步任务 |
process.nextTick | 微任务队列最前(比 Promise 更早) | ❌ 不存在 |
queueMicrotask | 微任务队列中 | 微任务队列中 |
Promise.then | 微任务队列中 | 微任务队列中 |
setTimeout | 宏任务(timers阶段) | 宏任务 |
setImmediate | check阶段的宏任务,晚于 setTimeout | ❌ 不存在 |
如果你写的 JavaScript 代码只使用浏览器和 Node.js 都支持的 API(如 setTimeout
、Promise
、queueMicrotask
等),那么它们的执行顺序在两者之间基本是没有区别的,原因如下:
❗只有使用 Node.js 专有 API,顺序才可能发生变化:
Node.js 专有 API | 浏览器支持 | 是否会造成执行顺序不同 |
---|---|---|
process.nextTick() | ❌ 不支持 | ✅ 会,优先级最高 |
setImmediate() | ❌ 不支持 | ✅ 会,晚于 setTimeout |
✅ 举个真实差异例子(只在 Node.js 有):
setTimeout(() => {console.log('setTimeout');
}, 0);setImmediate(() => {console.log('setImmediate');
});
在 Node.js 中:
-
有时输出顺序是:
setTimeout setImmediate
-
有时是:
setImmediate setTimeout
(取决于当前事件循环阶段,极其敏感)
在浏览器中:
-
setImmediate
根本不存在,会报错或被忽略。
✅ 浏览器和 Node.js 都支持的任务类别:
任务类型 | 优先级 | 支持环境 | 示例代码 |
---|---|---|---|
同步任务 | 最高 | 浏览器 + Node | console.log('sync') |
微任务(Promise) | 高 | 浏览器 + Node | Promise.resolve().then(...) |
微任务(queueMicrotask) | 高 | 浏览器 + Node | queueMicrotask(...) |
宏任务(setTimeout) | 低 | 浏览器 + Node | setTimeout(..., 0) |
如果你不使用 Node.js 特有的 API(如
process.nextTick
、setImmediate
),你的代码在浏览器和 Node.js 中的执行顺序基本是一样的。
要对比事件循环机制的差异,必须涉及到 Node.js 独有的任务调度机制。如果你需要可视化事件循环对比图,我可以马上生成一张图来帮助你更直观理解。是否需要?
如果你希望我给出一张图来说明「浏览器 vs Node.js 事件循环对比图」,我可以画一个视觉化的流程。是否需要?
四、NodeJS11和10以及之前的事件循环执行差异
版本 | 微任务执行时机 |
---|---|
Node.js ≤10 | 所有宏任务执行完后统一跑微任务 |
Node.js ≥11 | 每个宏任务后立即跑一次微任务队列 |
Node.js 不同版本的事件循环行为确实是有差异的,尤其体现在宏任务执行后的微任务调度时机这一点上,Node 10 与 Node 11+(尤其是 Node 11.0.0 到 11.1.0)之间存在关键差别。以下是完整解释:
✅ 1. Node.js 中事件循环阶段回顾
Node.js 事件循环分为 6 个阶段:
-
timers(定时器,如
setTimeout
,setInterval
) -
pending callbacks
-
idle, prepare
-
poll
-
check(
setImmediate
) -
close callbacks
微任务(microtasks) 包括:
-
process.nextTick
(Node 专有,优先级高于所有微任务) -
Promise.then
、queueMicrotask
(ECMAScript 标准微任务)
✅ 2. Node.js 不同版本的行为差异对比
🔸 Node 10 及之前版本
在 timers 阶段,如果定时器回调队列中有多个任务,先执行完所有这些任务,再执行一次微任务队列。
-
所以:所有
setTimeout
执行完 → 再跑一次微任务队列。
🔸 Node 11+(尤其从 v11.0.0 开始)
Node 11+ 引入了浏览器类似的行为:
每执行一个宏任务(例如一个定时器回调),就执行一次微任务队列,也就是:
-
setTimeout 1 → microtasks → setTimeout 2 → microtasks ...
这种行为更贴近浏览器的事件循环机制。
✅ 3. 举个例子对比
setTimeout(() => {console.log('timeout1');Promise.resolve().then(() => console.log('promise1'));
}, 0);setTimeout(() => {console.log('timeout2');Promise.resolve().then(() => console.log('promise2'));
}, 0);
在 Node 10:
timeout1
timeout2
promise1
promise2
在 Node 11+:
timeout1
promise1
timeout2
promise2
✅ 4. 总结:Node.js 中宏任务执行后微任务处理机制的版本演化
Node.js 版本 | 宏任务后微任务调度行为 | 类似于浏览器 |
---|---|---|
≤ v10 | 所有宏任务执行完 → 再统一执行微任务队列 | ❌ 否 |
v11+(含) | 每个宏任务执行完后 → 立即执行微任务队列 | ✅ 是 |
v16+(现代主流) | 保持浏览器兼容行为,符合 Promises/A+ 规范 | ✅ 是 |
🔧 查看当前 Node.js 版本
你可以运行:
node -v
查看当前版本以决定代码执行行为。