相信在面试的小伙伴可能都遇到过问关于event loop
。event loop
在前端算是一个比较重要的知识点,如果你对它不是很了解,有些逻辑你可能会屡错,因为它涉及到代码的执行顺序问题,下面就由我来带你掌握它。
因为js
是单线程的,如果某段程序需要等待一会再执行,后面的程序都会被阻塞,这样也就带来了一些问题。为了解决这个问题,js
出现了同步和异步两种任务,两种任务的差异就在于执行的优先级不同。event loop
就是对任务的执行顺序做了详细的规范。
异步任务:异步任务分为宏任务和微任务。
常见的微任务有:Promise.then()
,.then
中的逻辑是微任务;process.nextTick(node环境)
。
常见的宏任务有:setTimeout、setInterval
、setImmediate(node环境)
、xhr(发送网络请求)
,callback
。
同步任务:除了上面的这些情况,都属于同步任务。
先到后:同步任务 -> 微任务 -> 宏任务。
无论是同步任务还是异步任务,都是在主线程执行。
事件循环(event loop)就是 任务在主线程不断进栈出栈的一个循环过程。任务会在将要执行时进入主线程,在执行完毕后会退出主线程。
下面就是这个循环的步骤:
1.把同步任务队列 或者 微任务队列 或者 宏任务队列中的任务放入主线程。
2.同步任务 或者 微任务 或者 宏任务在执行完毕后会全部退出主线程。
在实际场景下大概是这么一个顺序:
1.把同步任务相继加入同步任务队列。
2.把同步任务队列的任务相继加入主线程。
3.待主线程的任务相继执行完毕后,把主线程队列清空。
4.把微任务相继加入微任务队列。
5.把微任务队列的任务相继加入主线程。
6.待主线程的任务相继执行完毕后,把主线程队列清空。
7.把宏任务相继加入宏任务队列。无time
的先加入,像网络请求。有time
的后加入,像setTimeout(()=>{},time)
,在他们中time
短的先加入。
8.把宏任务队列的任务相继加入主线程。
9.待主线程的任务相继执行完毕后,把主线程队列清空。
下面来看一道题目来更好的理解event loop
。
setTimeout(()=>{console.log(6)},2000) // 任务a
setTimeout(()=>{console.log(4)}) // 任务 b
setTimeout(()=>{console.log(5)}) // 任务 c
new Promise((rel)=>{
console.log(1) // 任务 d
rel(3)
})
.then((res)=>{console.log(res)}) // 任务 e
console.log(2) // 任务 f
// 打印结果:1,2,3,4,5,6
下面使用event loop
的概念来讲解上面的题目,总共四次循环:
const tasks=[] // 主线程任务队列
const syncTasks=[] // 同步任务队列
const tinyTasks=[] // 微任务队列
const macroTasks=[] // 宏任务队列
// 第一次
syncTasks.push('d','f') // 首先同步任务d,f相继加入同步任务队列
for(let t of syncTasks){tasks.push(syncTasks.pop())} // 然后把同步任务队列的任务相继加入主线程
console.log(tasks) // ['d','f']
for(let t of tasks){tasks.pop()} // 在主线程相继执行完毕后,会相继出栈
console.log(tasks) // []
// 第二次
tinyTasks.push('e') // 首先微任务e加入微任务队列
for(let t of tinyTasks){tasks.push(tinyTasks.pop())} // 然后把微任务队列的任务相继加入主线程
console.log(tasks) // ['e']
for(let t of tasks){tasks.pop()} // 在主线程相继执行完毕后,会相继出栈
console.log(tasks) // []
// 第三次
macroTasks.push('b','c') // 首先宏任务'b','c'加入宏任务队列
for(let t of macroTasks){tasks.push(macroTasks.pop())} // 然后把宏任务队列的任务相继加入主线程
console.log(tasks) // ['b','c']
for(let t of tasks){tasks.pop()} // 在主线程相继执行完毕后,会相继出栈
console.log(tasks) // []
// 第四次
macroTasks.push('a') // 首先宏任务a加入宏任务队列
for(let t of macroTasks){tasks.push(macroTasks.pop())} // 然后把宏任务队列的任务相继加入主线程
console.log(tasks) // ['a']
for(let t of tasks){tasks.pop()} // 在主线程相继执行完毕后,会相继出栈
console.log(tasks) // []
相信看完这篇文章的小伙伴一定对event loop
有了很好的理解,下次再遇到关于它你会发现它其实很简单。感谢你对观看,希望这篇文章能给你带来快乐。如果有小伙伴有一些问题或者疑惑,欢迎提出和分享。