宏任务和微任务

5/10/2021 js基础

# 什么是事件循环

JS 引擎是单线程的。JS做的任务分为同步和异步两种,所谓 "异步",简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。

如果没有异步操作,在碰到一些花费时间很长的操作时,JS这种这种单线程语言会等待结果,这种长时间空闲等待是不可接受的,所以JS采用了“异步任务回调”的模式。

在等待异步任务准备的同时,JS引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。这种模式的优势显而易见,完成相同的任务,花费的时间大大减少,这种方式也被叫做非阻塞式。

而实现这个的,正是事件循环。

事件循环是由一个队列组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。

根据队列中任务的不同,分为宏任务和微任务。

# 宏任务和微任务

事件循环由宏任务和微任务组成,完成当下的宏任务后,会执行在此期间入队的微任务。

常见的宏任务有: script/setTimout/setInterval/requestAnimationFrame/UI render

常见的微任务有:Promise.then()/Object.observe/process.nextTick(node 独有)

# 聊一聊浏览器的事件循环

浏览器首先执行宏任务:script脚本。碰到宏任务和微任务时加入各自队列中,在script执行完成后,执行当前微任务队列。这是一次事件循环。

然后再取出一个宏任务,再将碰到的任务加入队列,然后再清空当前微任务队列。

...

需要注意的是每个宏任务都有自己的微任务队列。每次都是一个宏任务执行完,再执行当前宏任务下的微任务队列。

经典例子:

  console.log('script1')
  Promise.resolve().then(()=>{
    console.log('第一个回调函数:微任务1')
    setTimeout(()=>{
      console.log('第三个回调函数:宏任务2')
    },0)
  })
  console.log('script2')
  setTimeout(()=>{
    console.log('第二个回调函数:宏任务1')
    Promise.resolve().then(()=>{
      console.log('第四个回调函数:微任务2')
    })
  },0)
  console.log('script3')

大致流程梳理如下,这个地方需要自己认真理解。

-> script开始执行 -> promise加入微任务1队列 -> settimeout加入新宏任务1
-> script执行完毕 -> 执行promise微任务1队列 -> settimeout加入新宏任务2 -> 微任务1队列执行完毕
-> 执行宏任务1 -> promise加入微任务2队列 -> 宏任务1执行完毕 -> 执行微任务2队列 -> 微任务2队列执行完毕
-> 执行宏任务2 -> 宏任务2执行完毕 -> over