js 和 node 里面的事件循环

js 和 node 里面的事件循环

前言: 之前有接触过 js 事件循环,感觉比较容易理解。后面学习《深入浅出node.js》的时候,发现里面提到的事件循环有点绕比较难理解,后面查了一些资料和博客后,发现 node 环境下的事件循环和我之前接触的事件循环是有一定的区别的。

一、js 的事件循环

废话不多说,先上图:

js 的事件循环,把异步任务分为两种:宏任务 (macroTask) 和微任务 (microTask)。对应也有两个队列来存储这些任务回调函数:宏任务队列和微任务队列。

在一次 tick(循环)里面,流程大概如下:
1. 如果宏任务队列不为空,从宏任务取出一个宏任务执行。
2. 如果微任务队列不为空,把微任务队列的全部微任务都执行了。
3. 然后如果 DOM 需要更新,就对 DOM 进行更新渲染。

二、node 的事件循环

2.1 node 通过事件循环来处理异步任务

在具体了解 node 是事件循环流程之前,我们先来看看 node 是如何处理异步任务的:

对于异步任务,node 做一种划分:I/O 和 非I/O。I/O 异步任务就扔给特定的线程池去处理,非 I/O 就自己来处理。(这就是为什么 node 适合搭建 I/O 密集型服务器而不适合 CPU 密集型服务器)。

而不管怎么处理,处理完了之后都会把回调函数注册成事件放入事件队列(这一步依赖事件对应的观察者),等待事件循环来处理。

拓展:node 下的多线程和单线程:

事件循环对回调函数的逻辑处理当然还是 js 单线程。

不过多线程依旧是 node 事件循环的基础。

在上图可以看到,对于异步I/O,node 不是直接处理的,而且扔给了 I/O 线程池来处理。(这个 I/O 线程池的具体实现在不同平台也不同,不过本质都是用线程池的多线程来处理,所以从 Libuv 层面可以理解为就是一个 I/O 线程池来处理)

2.2 node 事件循环里一次 tick 的具体流程

在大概了解了 node 的异步 I/O 模型后,我们再来看看 node 事件循环里一次 tick 的具体流程,了解这个,我们才能知道 node 事件循环和 js 事件循环对于代码执行顺序的控制究竟有什么不同。

废话不多说,上图:

是不是看着很乱,一共有6个阶段。但其实我们正常开发一般只会涉及到 3 个阶段:
1. timer 阶段:处理 setTimeout 和 setInterval 的回调
2. poll 阶段:处理异步 I/O 完成后的回调
3. check 阶段:处理 setImmediate 的回调

node 事件循环 6 个阶段处理的事件,都有一个先入先出的事件队列来进行存储。每一次 tick 的时候,会按照上图的阶段顺序去处理这 6 个阶段的事件队列的全部事件回调函数。(和 js 一次tick 处理一个宏任务不同)

那么,微任务队列呢?node 里面的微任务大概有两种:
1. process.nextTick 回调
2. resolve 的 Promise 回调

node 依旧是一次性将微任务队列里的微任务全部处理。而发生的时机,大部分是在事件循环的 6 个事件处理阶段之后。(js 事件循环则是执行完宏任务之后)

为什么说大部分阶段呢,因为有一个阶段是特殊的,就是 poll 轮询阶段。它特殊在清空微任务队列的时机:它是执行完毕 poll 队列里面的一个回调函数后就清空一次微任务队列。

posted @ 2020-10-27 19:53  树干  阅读(406)  评论(0编辑  收藏  举报