今天去看eventloop事件循环的时候,看了很多文章,越看越模糊,整得很乱,放了很多图,哈哈,对不起亲们,我确实看的云里雾里,不如自己凭着这些文章的大致思路,来梳理一下自己能够理解的过程
js
在说这个流程之前,我们先来说一下,几个概念,同步代码,宏任务,微任务,栈,堆,同步任务,异步任务,单线程
大家都知道,js 是单线程,node 也是单线程,所谓的单线程就是同时只能做一件事情,其他的事情在后面排,像招商银行的客服一样,one by one and next 主线程就是 那个 单线程 但是要搞清楚,js是单线程,宿主环境可能不是单线程,浏览器可以是多线程,node环境是单线程
概念
同步代码 和 同步任务其实是一个概念,是执行器从上至下能立刻完成的事件,不会被引擎挂起、不需要过多损耗,发费过长的事件去完成的事件,同步任务的任务队列就放在栈里面,栈区存放的都是一些简单的事情,
异步任务,是浏览器在将来某个时间 或者说 以后满足某一条件等才会去执行的一个任务,它被放在了一个任务队列里面,当主线程处理完成后,会去处理任务队列的异步任务。
那么 不同的异步任务根据性质不一样,分为两类,就是 宏任务 和 微任务,所以,同步任务是包含两种任务,一种是宏任务,一种是微任务
宏任务 和 微任务 是怎么区分来着,为什么这个就是宏任务,这个就是微任务呢,这里说不得,太长了, 那就不管上面的概念,我记住哪一个是宏 和 微 就OK了,
这里写了一个表格,在node环境和js环境 不同的事件区分的任务种类还有些不一样
任务事件 | node环境 | 浏览器环境 |
---|---|---|
IO | 宏任务 | 宏任务 |
setTimeOut,setInterVal | ||
setImmediate | ❎ | |
requestAnimationFrame | ❎ | 宏任务 |
process.nextTick | ❎ | 微任务 |
MutationObserver | 微任务 | ❎ |
Promise.then catch finally | 微任务 | 微任务 |
async/await函数中的后续操作 | 微任务 | 微任务 |
堆和栈: js代码执行的时候会将不同的变量存于内存中的不同位置就是堆栈的位置, 堆(heap)和栈(stack)
其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。
怎么记忆他们呢,
堆,一堆,很多,存放的东西很多,就是一堆,就是堆, 引用数据类型,对象,存很多东西,放在这里,
其余的就是栈,栈中存放着一些基础类型变量以及对象的指针。相对比较简单一些的东西
但是执行栈 和 栈 是两个概念, 执行栈可以简单理解执行上下文的环境
ok, 概念简单说完了,说说整体的eventLoop
主线程会把同步任务(同步代码)依次执行,在执行同步代码的同时,可能会产生异步任务,可能是宏任务,也可能是微任务,把这些任务依次放进一个队列里面,在同步任务执行完成后,会去执行异步任务, 这里不说什么 monitoring process,Event table, Event queue , 简单点
好,同步代码执行完成,在此期间 ,会产生宏任务, 可能会有微任务, 如果有微任务,那么肯定是要先去执行微任务,再去执行宏任务
但是很多文章都是说,宏任务执行期间,如果有微任务进入,在当前的宏任务执行完毕后,优先执行剩下的微任务,微任务是紧急插入事件,需要优先执行,就是下面这张图
开头在哪里,异步任务应该有一个开头,如果开头有微任务,和宏任务呢,为啥不直接说呢
看的我心慌 ,到底是不是先执行微任务,在执行宏任务,
结论
那,结论就出来了: 主线程会把同步任务(同步代码)依次执行,在执行同步代码的同时,可能会产生异步任务,可能是宏任务,也可能是微任务,把这些任务依次放进一个队列里面,在同步任务执行完成后,会去执行异步任务,异步任务中,如果存在微任务,先执行微任务,在执行宏任务,如果在宏任务执行中产生了微任务,那么在本次宏任务执行完毕后,执行剩余的微任务,微任务中如果还存在微任务,接着执行,直到执行完毕,在去执行下一个宏任务。
额外的
说一些额外的:
-
异步任务不在主线程里面执行,在宿主环境执行,执行完的任务有回调函数,会放在队列里,按顺序依次等待主线程来执行(想想平时请求的接口)
-
宏任务和微任务放的不是一个队列,是两个队列,先执行的永远是微任务的队列
node
Node.js 是一个新的 JS 运行环境,它同样要支持异步逻辑,包括定时器、IO、网络请求,很明显,也可以用 Event Loop 那一套来跑。
但是呢,浏览器那套 Event Loop 就是为浏览器设计的,对于做高性能服务器来说,那种设计还是有点粗糙了。
哪里粗糙呢?
浏览器的 Event Loop 只分了两层优先级,一层是宏任务,一层是微任务。但是宏任务之间没有再划分优先级,微任务之间也没有再划分优先级。
而 Node.js 的宏任务之间也是有优先级的,比如定时器 Timer 的逻辑就比 IO 的逻辑优先级高,因为涉及到时间,越早越准确;而 close 资源的处理逻辑优先级就很低,因为不 close 最多多占点内存等资源,影响不大。
于是就把宏任务队列拆成了五个优先级:Timers、Pending、Poll、Check、Close。
解释一下这五种宏任务:
Timers Callback:涉及到时间,肯定越早执行越准确,所以这个优先级最高很容易理解。
Pending Callback:处理网络、IO 等异常时的回调,有的 *niux 系统会等待发生错误的上报,所以得处理下。
Poll Callback:处理 IO 的 data,网络的 connection,服务器主要处理的就是这个。
Check Callback:执行 setImmediate 的回调,特点是刚执行完 IO 之后就能回调这个。
Close Callback:关闭资源的回调,晚点执行影响也不到,优先级最低。
所以呢,Node.js 的 Event Loop 就是这样跑的了:
还有一点不同要特别注意:
Node.js 的 Event Loop 并不是浏览器那种一次执行一个宏任务,然后执行所有的微任务,而是执行完一定数量的 Timers 宏任务,再去执行所有微任务,然后再执行一定数量的 Pending 的宏任务,然后再去执行所有微任务,剩余的 Poll、Check、Close 的宏任务也是这样。
为什么这样呢?
其实按照优先级来看很容易理解:
假设浏览器里面的宏任务优先级是 1,所以是按照先后顺序依次执行,也就是一个宏任务,所有的微任务,再一个宏任务,再所有的微任务。
而 Node.js 的 宏任务之间也是有优先级的,所以 Node.js 的 Event Loop 每次都是把当前优先级的所有宏任务跑完再去跑微任务,然后再跑下一个优先级的宏任务。
也就是一定数量的 Timers 宏任务,再所有微任务,再一定数量的 Pending Callback 宏任务,再所有微任务这样。
为什么说是一定数量呢?
因为如果某个阶段宏任务太多,下个阶段就一直执行不到了,所以有个上限的限制,剩余的下个 Event Loop 再继续执行。
除了宏任务有优先级,微任务也划分了优先级,多了一个 process.nextTick 的高优先级微任务,在所有的普通微任务之前来跑。
所以,Node.js 的 Event Loop 的完整流程就是这样的:
- Timers 阶段:执行一定数量的定时器,也就是 setTimeout、setInterval 的 callback,太多的话留到下次执行
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
- Pending 阶段:执行一定数量的 IO 和网络的异常回调,太多的话留到下次执行
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
- Idle/Prepare 阶段:内部用的一个阶段
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
- Poll 阶段:执行一定数量的文件的 data 回调、网络的 connection 回调,太多的话留到下次执行。如果没有 IO 回调并且也没有 timers、check 阶段的回调要处理,就阻塞在这里等待 IO 事件
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
- Check 阶段:执行一定数量的 setImmediate 的 callback,太多的话留到下次执行。
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
- Close 阶段:执行一定数量的 close 事件的 callback,太多的话留到下次执行。
- 微任务:执行所有 nextTick 的微任务,再执行其他的普通微任务
比起浏览器里的 Event Loop,明显复杂了很多,但是经过我们之前的分析,也能够理解:
结论
Node.js 对宏任务做了优先级划分,从高到低分别是 Timers、Pending、Poll、Check、Close 这 5 种,也对微任务做了划分,也就是 nextTick 的微任务和其他微任务。执行流程是先执行完当前优先级的一定数量的宏任务(剩下的留到下次循环),然后执行 process.nextTick 的微任务,再执行普通微任务,之后再执行下个优先级的一定数量的宏任务。。这样不断循环。其中还有一个 Idle/Prepare 阶段是给 Node.js 内部逻辑用的,不需要关心。
改变了浏览器 Event Loop 里那种一次执行一个宏任务的方式,可以让高优先级的宏任务更早的得到执行,但是也设置了个上限,避免下个阶段一直得不到执行。
不同点:
- 浏览器执行宏任务没有优先级,依次执行,并且执行完成本次所有的宏任务,node宏任务有优先级,只能执行一定的宏任务,
- 浏览器执行微任务无优先级,依次执行,node 有优先级,先执行 process.nextTick,在执行剩下的微任务。
如有错误,还请指出,加以改正,感谢。