一文清晰浏览器事件循环和执行顺序

124 阅读3分钟

什么是事件循环

浏览器是单线程的,为了保证同步和异步任务的执行顺序,有了自己一套规则。

浏览器所有的事件类型

  1. 同步任务:如变量的声明,函数的声明
  2. 异步任务:异步任务分为宏任务、微任务
  3. 宏任务:如setTimeout、setInterval、浏览器的I/O等
  4. 微任务:await的下一行、promise 的then或者catch、vue中的nextTick()等

执行顺序

优先级: 同步任务->微任务->宏任务

这是几种任务的优先级。也就是先执行主线程中的宏任务(可以理解按代码书写顺序执行),而在执行宏任务之前先执行在微任务队列中的微任务。

console.log(1) // 同步任务

const pA = new Promise((resolve, reject)=>{
    resolve(2)
})
pA.then((resolve)=>{
    console.log(resolve)
}) // 微任务
setTimeout(()=>{
    console.log(3)
},0) // 宏任务
const pB = new Promise((resolve, reject)=>{
    resolve(4)
})

pB.then((resolve)=>{
    console.log(resolve)
}) // 微任务

console.log(5)

根据描述,你可以猜一下以上代码执行顺序,如果你的结果是"1,5,2,4,3"那么很好你掌握了 浏览器各个任务执行的顺序。

解释

第1行是同步任务,直接输出1。

第7、10、17行是异步任务,分别进入微任务队列和宏任务队列等待执行

第20行是同步任务,直接输出5。

同步任务执行完了,开始执行异步任务

先执行微任务7,17 输出2,4

后执行10 输出3

最终结果是"1,5,2,4,3"

练习

在开发中往往更加的复杂。比如:

async function foo(){
  console.log('foo')
  setTimeout(()=>{
    console.log("11")
  },0)
}
async function bar(){
  console.log('bar start')
  await foo()
  console.log("bar end")
}
console.log('script start')
setTimeout(function(){
  console.log('setTimeout')
},0)
bar();
new Promise(function(resolve){
  console.log('promise executor')
  resolve();
}).then(function(){
  console.log('promise then')
})
console.log('script end')

"script start"
"bar start"
"foo"
"promise executor"
"script end"
"bar end"
"promise then"
"setTimeout"
"11"
解释

首先运行第12行同步任务,直接输出"script start",14行是宏任务,加入宏任务队列。 进入第16行进入bar(), bar()中的第8行是同步任务,直接输出"bar start";

此时微任务队列[ ], 此时宏任务队列[ 14]

到达第9行是一个异步任务并且加了await, await 后面是个异步但不是promise 对象,如果是promsie 对象需要等待resolve()获取reject(),所以执行完同步任务第3行直接跳出foo();输出"foo",并把第4行加入宏任务队列中。

此时微任务队列[ ], 此时宏任务队列[14,4]

即便是第9行不是await 一个promise 对象,但是await 会包装一个非promise对象为promise 对象并执行resolve,也就是await 后面,即第10行是个微任务,直接加入微任务队列。

此时微任务队列[10], 此时宏任务队列[14,4]

继续在Promise的声明里面,所以是个同步任务,输出"promise executor";

此时微任务队列[10], 此时宏任务队列[14,4]

21 行为then里面是微任务即21行是微任务,加入微任务队列,进入23行同步任务执行输出"script end"

此时微任务队列[10,21], 此时宏任务队列[14,4]

到这里所有的同步任务执行完成,进入微任务队列,第一微任务是第10行输出"bar end"

此时微任务队列[21], 此时宏任务队列[14,4]

继续执行队列中的微任务21行输出'promise then'

此时微任务队列[ ], 此时宏任务队列[14,4]

到这里微任务队列里面没有微任务了,开始执行宏任务队列,此时14行输出"setTimeout"

此时微任务队列[ ], 此时宏任务队列[4]

再执行第二个宏任务,输出等4行"11"。

此时微任务队列[ ], 此时宏任务队列[ ]