js事件循环 VS Node事件循环

·  阅读 354
js事件循环 VS Node事件循环

写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 属于宏任务还是微任务???

分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改