前言
大家是不是经常会碰到一些setTimeout,promise,async等面试题问你输出的顺序,这是我们前端很头痛的问题,让我们一起来了解一下前端的事件循环机制吧。
浏览器中的事件循环
因为JS是单线程所以JS引擎(如V8)在同一时刻只能处理一个任务,所以当js引擎遇到一个异步任务后并不会一直等待其返回结果,而是会将这个任务加入事件队列,继续执行执行栈中的其他任务,当主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环”。
执行异步任务的时候还有两种分类macro-task(宏任务)和micro-task(微任务)。
宏任务和微任务
macro-task大概包括:
- setTimeout
- setInterval
micro-task大概包括:
- Promise.then
总的来说流程就是:执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环,同一次事件循环中,微任务永远在宏任务之前执行。
我们拿一段代码来理解一下:
console.log('script start')
async function async1() {
await async2()
console.log('async1')
}
async function async2() {
console.log('async2')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
执行结果:
结果分析:
主程序按循序执行了script start=>async2=>Promise=>script end,然后异步队列中微任务(async1,promise1,promise2)宏任务(setTimeout),按先微任务后宏任务的顺序执行。
这里的await需要注意一下,可以理解成await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码。