Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它采用了事件驱动、非阻塞 I/O 的模型,使得它在处理高并发、I/O 密集型应用方面表现出色。在 Node.js 的背后,有一个重要的机制:事件循环(Event Loop)。本文将详细介绍 Node.js 的事件循环机制,包括事件循环的阶段、事件队列、定时器、I/O 操作等内容,希望对读者理解 Node.js 的运行机制有所帮助。
事件循环的阶段
在 Node.js 中,事件循环被分为几个阶段,每个阶段都有自己的任务队列和回调函数。事件循环的阶段如下:
- timers 阶段:执行 setTimeout() 和 setInterval() 中到期的回调函数。
- pending callbacks 阶段:执行系统级别的回调函数,比如 TCP 错误的回调函数。
- idle, prepare 阶段:仅在内部使用。
- poll 阶段:处理 I/O 事件,执行 I/O 回调函数。当事件循环进入 poll 阶段时,如果没有设定定时器,且没有其他任务需要处理,事件循环将会阻塞在这里,等待新的事件加入队列。如果有定时器设定,事件循环将会跳过 poll 阶段,直接进入 timers 阶段。
- check 阶段:执行 setImmediate() 回调函数。
- close callbacks 阶段:执行关闭的回调函数,比如 socket.on('close', ...)。
在事件循环的每个阶段,Node.js 都会执行相应的回调函数,直到所有任务都被处理完毕,事件循环才会结束。
事件队列
在 Node.js 中,事件队列是一个先进先出的队列,用于存储回调函数。当某个事件被触发时,它会被加入到事件队列中,等待事件循环的处理。
事件队列分为两种类型:Macrotask 和 Microtask。Macrotask 包括 I/O 操作、定时器和 setImmediate(),它们的回调函数会被加入到事件队列的队尾。Microtask 包括 Promise 和 process.nextTick(),它们的回调函数会被加入到事件队列的队首。
Node.js 在每个事件循环阶段结束时,都会先处理 Microtask 队列中的所有任务,然后再处理 Macrotask 队列中的任务。这意味着,如果在一个事件循环阶段中,不断向 Microtask 队列中添加任务,那么 Macrotask 队列中的任务就会被一直阻塞,直到 Microtask 队列中的任务全部执行完毕。
定时器
在 Node.js 中,定时器有两种类型:setTimeout() 和 setInterval()。它们都可以用来设定一段时间后执行某个回调函数,但有一些细微的差别。
setTimeout() 只会执行一次回调函数,而 setInterval() 会每隔一段时间执行一次回调函数。此外,如果 setInterval() 的回调函数执行时间超过了设定的时间间隔,那么下一次回调函数的执行时间就会延迟。
在 Node.js 中,定时器的最小时间间隔是 1 毫秒。如果设定了一个小于 1 毫秒的时间间隔,Node.js 会将其视为 1 毫秒。
I/O 操作
在 Node.js 中,I/O 操作是通过异步的方式进行的,它们不会阻塞事件循环。当一个 I/O 操作完成时,Node.js 会将其对应的回调函数加入到事件队列中,等待事件循环的处理。
对于一些 I/O 操作,Node.js 提供了一些带回调函数的 API,比如 fs.readFile()、http.request() 等。当这些 API 被调用时,它们会将回调函数加入到事件队列中,然后立即返回。这样,后续的代码就可以继续执行,而不需要等待 I/O 操作的完成。
示例代码
下面是一个简单的示例代码,用来演示 Node.js 的事件循环机制:
-- -------------------- ---- ------- --------------------- ------------- -- - ----------------------- -- --- --------------- -- - ------------------------- --- ------------------- -- - ------------------------ --- -------------------
运行上面的代码,输出结果如下:
start end nextTick immediate timeout
可以看到,事件循环的执行顺序是先处理 Microtask 队列中的任务(process.nextTick()),然后处理 Macrotask 队列中的任务(setImmediate() 和 setTimeout())。对于 setTimeout(),即使设定的时间间隔为 0,它的回调函数也会被放到事件队列的队尾,因此它的执行顺序在 setImmediate() 后面。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d37b1ea941bf713469a75b