js 事件循环消息队列和微任务宏任务

Posted 贪吃ღ大魔王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js 事件循环消息队列和微任务宏任务相关的知识,希望对你有一定的参考价值。


一、事件循环与消息队列

因为js是单线程脚本语言,一般情况下代码是同步执行。也就是说js执行代码是一行一行向下执行的,前面没有执行完成是不会执行后面的代码的。

同步和异步的区别其实就在于需不需要排队的问题

同步:所有任务一视同仁,都得排队,先来后到;
异步:可以按照一定规则(不至于乱套)插队执行;

事件循环和消息队列怎么理解

事件循环:单线程脚本语言javascript处理任务的一种执行机制,通过循环来执行任务队列里的任务。这个执行过程形象的称之为事件循环
消息队列:js为单线程脚本语言,执行任务时需要排队,每当有新的任务来临时就加到这个队列后面。这个队列就叫消息队列或者任务队列

二、浏览器与Node的事件循环有何区别?

1.浏览器事件循环过程

 当某个宏任务执行完后,会查看是否有微任务队列。
  如果有,先执行微任务队列中的所有任务,
  如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。
  栈空后,再次读取微任务队列里的任务,依次类推。

2.node事件循环过程

1.外部输入数据
2.轮询阶段(poll)
3.检查阶段(check)
3.关闭事件回调阶段(close callback)
4.定时器检测阶段(timer)
5.I/O事件回调阶段(I/O callbacks)
6.闲置阶段(idle, prepare)
7.轮询阶段(按照该顺序反复运行)...

timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
idle, prepare 阶段:仅node内部使用
poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
check 阶段:执行 setImmediate() 的回调
close callbacks 阶段:执行 socket 的 close 事件回调

注意:
上面六个阶段都不包括 process.nextTick(),这个函数其实是独立于事件循环之外的,它有一个自己的队列,当每个阶段完成后,
如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他微任务执行

三、微任务和宏任务

在js中,任务可以分为同步任务和异步任务,也可以分为微任务和宏任务。同步任务属于宏任务,有了这些划分,就可以保证所有任务都有条不紊的执行下去,总的来说就是给要执行的任务定了执行规则、划分了优先级。
在总结宏任务与微任务时,我们先要知道我们哪些情况下可能会执行异步操作(未来某个时间执行任务);然后要知道宏任务与微任务是怎么区分的,哪些属于宏任务,哪些属于微任务;最后我们要知道宏任务与微任务是通过什么规则来配合执行的。

1.可能存在异步执行的情况

回调函数 callback
Promise/async await
Generator 函数
事件监听
发布/订阅
计时器
requestAnimationFrame
MutationObserver
process.nextTick
I/O

2.宏任务

所有的同步任务
I/O, 比如文件读写、数据库数据读写等等
window.setTimeout
window.setInterval
window.setImmediate
window.requestAnimationFrame

3.微任务

Promise.then catch finally
Generator 函数
async await 和promise是一样的,属于微任务
MutationObserver

四、任务执行过程

所有任务都在主进程上执行,异步任务会经历2个阶段 Event Table和Event Queue
同步任务在主进程排队执行,异步任务(包括宏任务和微任务)在事件队列排队等待进入主进程执行
遇到宏任务推进宏任务队列,遇到微任务推进微任务队列(宏任务队列的项一般对应一个微任务队列,有点像一个大哥带着一群小马仔,这就组成一组异步任务。如果有嵌套那就会有多个大哥小马仔)
执行宏任务,执行完宏任务,检查有没有当前层的微任务(大哥带着小马仔逐步亮相。。。)
继续执行下一个宏任务,然后执行对应层次的微任务,直到全部执行完毕(下一个大哥带着他的小马仔亮相。。。)

五、举例

//因为涉及到process 所以应该在node环境下执行  


console.log('1') //主进程 执行 
setTimeout(function() {
   console.log('2')   //因为setTimeout是宏任务,所以加入宏任务队列1,['2']
   process.nextTick(function() {
     console.log('3') //因为 process.nextTick是微任务,所以加入微任务队列2,['4','3']
  })
  new Promise(function(resolve) {
      console.log('4') //因为此处代码执行不属于异步,所以直接推入主程序执行,['4']
      resolve()
  }).then(function() {
    console.log('5') // 因为promise then 是微任务,所以推入微任务队列2,['4','3','5']
  })
},0)
// process.nextTick总是发生在所有异步任务之前
process.nextTick(function() {
  console.log('6')  //因为process.nextTick是微任务,所以推入微任务队列1,['6']
  new Promise(function(resolve) {
    console.log('7')//因为此处代码执行不属于异步,所以直接推入主程序执行,['6','7']
    resolve()
  }).then(function() {
    console.log('8')//因为 promise then 是微任务,所以推入微任务队列1,['6','7','8']
  })
  setTimeout(function() {
    console.log('9')//因为setTimeout是宏任务,所以推入宏任务队列2 ,['9']
    process.nextTick(function() {
      console.log('10')//因为process.nextTick是微任务,所以推入微任务队列3,['9','11','12','10']
    })
    new Promise(function(resolve) {
      console.log('11')//因为此处代码执行不属于异步,所以直接推入主程序执行,['9','11']
      resolve()
      console.log('12')因为此处代码执行不属于异步,所以直接推入主程序执行,['9','11','12']
    }).then(function() {
      console.log('13')//因为 promise then 是微任务,所以推入微任务队列3,['9','11','12','10','12']
    })
  },0)
})

//打印输出
// 1
// 6
// 7
// 8
// 2
// 4
// 3
// 5
// 9
// 11
// 12
// 10
// 13

以上是关于js 事件循环消息队列和微任务宏任务的主要内容,如果未能解决你的问题,请参考以下文章

详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

宏任务和微任务

一篇搞定(Js异步事件循环与消息队列微任务与宏任务)

一篇搞定(Js异步事件循环与消息队列微任务与宏任务)

宏任务和微任务的一个小事

事件循环(event loop)