写Node的异步I/O的时候感觉事件循环还有到多东西可以写, 这篇专门拿来学习事件循环, 对比下JavaScript与Node.js事件循环的区别, 以及一些以前忽略的知识
事件队列
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。
宏任务 PK
graph TD
JavaScript宏任务 --> 鼠标/键盘等外设
JavaScript宏任务 --> script
JavaScript宏任务 --> setTimeout/setInterva
Node宏任务 --> I/O
Node宏任务 --> setTimeout/setInterva
Node宏任务 --> setImmediate
微任务PK
graph TD
JavaScript微任务 --> Promise.then
Node微任务 --> Promise.then
Node微任务 --> process.nextTick
可以看出Node在浏览器异步任务基础上还增加一些自己的异步任务。
JavaScript 事件循环
js在执行一段代码时, 会将同步任务放到执行栈中依次执行, 当遇到异步任务会交给
其它线程
处理, 等执行栈中的同步任务全部执行完, 再去队列中取已完成的异步任务的回调(比如setTimeOut(callback) 把这个callback取出来放到执行栈中执行), 遇到异步任务后又交给其它线程处理 (上面取出来的异步任务回调又是异步任务)
首先我们要知道js是单线程的没错,但是浏览器是多线程的, 除了js引擎线程
,还有定时器线程
, http请求线程
等, 浏览器不但有很多线程,还有很多进程: 渲染进程、 GPU进程、网络进程 ...
对于进程与线程这里就不介绍了, 网上找了个比喻, 大家可以简单记住:
进程 = 火车 线程 = 车厢
一个进程可以有多个线程(一辆火车有多个车厢)
Node 事件循环
当接收到请求时, 就将这个请求作为事件放入队列, 然后继续接收其它请求, 直到没有请求时(主线程空闲时), 开始循环事件队列, 这里要判断, 如果是非I/O任务 就直接处理, 如果是I/O任务 就从 线程池 中拿出一个线程来处理这个事件, 并指定回调函数, 然后继续循环队列其它事件。
在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
复制代码
官方解释如下
- 定时器:本阶段执行已经被
setTimeout()
和setInterval()
的调度回调函数。 - 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和
setImmediate()
调度的之外),其余情况 node 将在适当的时候在此阻塞。 - 检测:
setImmediate()
回调函数在这里执行。 - 关闭的回调函数:一些关闭的回调函数,如:
socket.on('close', ...)
。
总结下来就是 Node 事件循环更加复杂!!:
-
浏览器的 Event Loop 只分了两层优先级,一层是宏任务,一层是微任务。但宏任务之间没有再划分优先级,微任务之间也没有再划分优先级, 而 Node.js 任务宏任务之间是有优先级的,比如定时器 Timer 的逻辑就比 IO 的逻辑优先级高
-
Node.js 的 Event Loop 并不是浏览器那种一次执行一个宏任务,然后执行所有的微任务,而是执行完一定数量的 Timers 宏任务,再去执行所有微任务,然后再执行一定数量的 Pending 的宏任务,然后再去执行所有微任务
据说很多人答错的面试题
for (var year = 0; year < 2022; year++) {
setTimeout(function() {
console.log('year=', year); //year = 2022
}, 1000);
}
console.log('year=', year); //year = 2022
复制代码
requestAnimationFrame
看到网上有人争论 requestAnimationFrame 属于宏任务还是微任务???