1. JS的单线程特性
JavaScript 语言从诞生之初就被设计为单线程运行环境,这与其最初的应用场景 ------ 浏览器端脚本执行密切相关。Node.js 基于 V8 引擎构建,完全继承了 JavaScript 的单线程特性。从技术实现来看,JavaScript 引擎(如 V8 引擎)内部维护着一个调用栈(Call Stack),用于记录函数的调用关系。
1.1 同步任务与异步任务的区分
在单线程模型中,任务被分为两大类:同步任务和异步任务。同步任务会直接进入调用栈依次执行,例如普通的函数调用、算术运算等;而异步任务则不会立即进入调用栈,而是由对应的异步 API(如定时器、I/O 操作、事件监听)处理,当异步操作完成后,相关回调函数会被加入 ** 任务队列(Task Queue)** 等待执行。
javascript
// 同步任务示例
console.log('开始执行同步任务');
function syncTask() {
for (let i = 0; i < 1000000; i++) {
// 模拟耗时操作
}
console.log('同步任务执行完毕');
}
syncTask();
// 异步任务示例
console.log('注册异步任务');
setTimeout(() => {
console.log('异步回调函数执行');
}, 0);
console.log('异步任务注册完成');
1.2 服务器IO问题
Node.js 通过以下机制突破了单线程的局限性,实现了高性能的服务器端应用:
(一)非阻塞 I/O 模型
Node.js 将所有 I/O 操作(如网络请求、文件读写)设计为异步非阻塞模式。当应用程序发起 I/O 请求时,Node.js 不会等待操作完成,而是立即返回并继续处理后续任务,I/O 操作的结果会通过事件循环通知应用程序。这种机制使得 Node.js 能够在单线程环境下同时处理数以万计的并发连接。
(二)与多线程的结合
尽管 Node.js 主线程是单线程的,但它通过底层的 libuv 库实现了与操作系统内核的交互,利用系统级的多线程处理 I/O 操作(如在 Windows 系统中使用 IOCP,在 Linux 系统中使用 epoll)。同时,Node.js 还提供了cluster模块,允许开发者创建多个工作进程,每个进程独立运行在不同的 CPU 核心上,从而充分利用多核 CPU 的性能。
(三)事件驱动架构
Node.js 的核心架构是事件驱动的,所有的异步操作最终都会转化为事件的触发和处理。应用程序通过监听事件(如connect、data、end等)来响应 I/O 操作的结果,事件处理器作为回调函数在事件循环中执行。这种架构使得 Node.js 的代码结构简洁高效,非常适合处理高并发、低延迟的网络应用。
其中,最最核心的,就是Node的事件驱动架构.
2. Node.js 事件驱动
2.1 浏览器的事件循环模型
JS主线程只有一个调用栈,其事件模型是通过调用栈 + 任务队列 的方式实现的。
浏览器通过调用栈管理函数的执行上下文,确保代码按顺序同步执行。遇到异步任务时,会将其放入任务队列,之后继续执行同步代码。
浏览器将异步任务的回调函数分为两类队列:宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)。两类队列的执行优先级不同(微任务先于宏任务执行),直接影响任务的调度顺序。
2.2 Node.js 事件驱动模型
Node.js 事件驱动将宏任务队列(Macro Task Queue)进一步细化为6个阶段,6个阶段的异步回调任务会按照顺序执行:
- timers :处理
setTimeout
/setInterval
的回调。 - I/O callbacks:(老IO事件,用户一般不使用)。
- idle, prepare:内部阶段,用户代码一般不参与。
- poll:获取新的 I/O 事件,处理未执行的回调。
- check :处理
setImmediate
的回调。 - close callbacks :处理
socket.destroy()
等关闭事件的回调。
2.3 Node.js 微任务
1. Promise 的回调(then
/catch
/finally
)
Promise 的 .then()
、.catch()
、.finally()
回调会被加入微任务队列。这是最常见的微任务类型。
javascript
console.log('start');
Promise.resolve()
.then(() => { console.log('Promise then 回调'); // 微任务 })
.catch(() => {})
.finally(() => { console.log('Promise finally 回调'); // 微任务(在 then 之后执行) });
console.log('end');
输出:
javascript
start
end
Promise then 回调
Promise finally 回调
2. async/await 语法糖
async方法中的await关键词会把await之后的所有内容打包为一个Promise放入微任务队列。
3. ### process.nextTick
:更高优先级的 "伪微任务"
Node.js 基于自身需求扩展了微任务类型,最典型的是 process.nextTick
,其优先级高于所有 ES 标准微任务。
process.nextTick
是 Node.js 特有的异步控制工具,其回调会被加入 nextTick 队列
。在当前同步代码执行完毕后,nextTick 队列
会优先于微任务队列执行(即使微任务队列已有任务)。
javascript
console.log('start');
process.nextTick(() => {
console.log('process.nextTick 回调'); // 优先于微任务
});
Promise.resolve().then(() => {
console.log('Promise then 回调'); // 微任务
});
console.log('end');
输出:
arduino
start
end
process.nextTick 回调
Promise then 回调