event loop 详解

引言

相信在面试的小伙伴可能都遇到过问关于event loopevent loop 在前端算是一个比较重要的知识点,如果你对它不是很了解,有些逻辑你可能会屡错,因为它涉及到代码的执行顺序问题,下面就由我来带你掌握它。

为什么会有event loop

因为js是单线程的,如果某段程序需要等待一会再执行,后面的程序都会被阻塞,这样也就带来了一些问题。为了解决这个问题,js出现了同步和异步两种任务,两种任务的差异就在于执行的优先级不同。event loop就是对任务的执行顺序做了详细的规范。

同步和异步任务

异步任务:异步任务分为宏任务和微任务
常见的微任务有:Promise.then().then中的逻辑是微任务;process.nextTick(node环境)
常见的宏任务有:setTimeout、setIntervalsetImmediate(node环境)xhr(发送网络请求)callback
同步任务:除了上面的这些情况,都属于同步任务。

任务的执行顺序

先到后:同步任务 -> 微任务 -> 宏任务。

任务在哪里执行

无论是同步任务还是异步任务,都是在主线程执行。

什么是 event loop

事件循环(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有了很好的理解,下次再遇到关于它你会发现它其实很简单。感谢你对观看,希望这篇文章能给你带来快乐。如果有小伙伴有一些问题或者疑惑,欢迎提出和分享。

你可能感兴趣的:(javascript)