面试官😏: 讲一下事件循环 ,顺便做道题🤪

1,639 阅读3分钟

题目

倔友们一起看一下把 ~

面试官 : 讲一下事件循环 , 顺便做了一道题🤪:下面字母的输出顺序是什么 ?

async function async1() {
  console.log('E'); 
  await async2();
  console.log('F');
}

async function async2() {
  console.log('G');
}

setTimeout(() => console.log('H'), 0);
async1(); 
new Promise((res) => {
  console.log('I'); 
  res();
}).then(() => console.log('J'))

这是一段涉及 JavaScript 中异步操作( async/await 、 Promise 、 setTimeout )的代码,要确定其执行顺序,需要了解这些异步机制的执行原理—— 事件循环机制(Event loop)

答案 : E 、 G 、 I 、 F 、 J 、 H

不清楚的倔友一起往下看 :

事件循环

事件循环是JavaScript中用于

  • 处理异步操作
  • 协调事件处理

在网页开发中,事件循环确保用户界面的响应性。例如,当用户点击按钮时,点击事件作为一个宏任务被放入任务队列,在合适的时机被处理。在处理网络请求时,发起请求是异步操作,不会阻塞其他代码执行,请求完成后,响应数据的处理函数会作为一个宏任务或微任务被放入任务队列,等待执行。

基本概念

JavaScript是单线程语言,同一时间只能执行一个任务,但可以通过事件循环来实现异步操作。事件循环允许JavaScript在执行同步任务的同时,处理各种异步任务,如网络请求、定时器、用户交互等,不会因为某个耗时的操作而阻塞整个程序的运行。

工作原理

  • 执行栈:也叫调用栈,是一种存储函数调用关系的数据结构。当函数被调用时,会被压入执行栈,函数执行完毕后从栈顶弹出。JavaScript引擎会按照顺序执行执行栈中的函数。
  • 任务队列:包括宏任务队列和微任务队列。
    • 宏任务包括 setTimeout 、 setInterval 、 setImmediate (Node.js环境)、I/O操作、UI渲染等
    • 微任务包括 Promise 的回调、 MutationObserver 等
  • 循环机制:事件循环首先会检查执行栈是否为空,如果为空,则检查微任务队列是否有任务。如果有微任务,就会依次执行微任务队列中的所有任务,直到微任务队列为空。然后,事件循环会检查宏任务队列,从宏任务队列中取出一个宏任务放入执行栈执行,执行完这个宏任务后,再次检查微任务队列并执行其中的任务,如此循环往复。

执行流程图

这张图说明了一下几个点 :

  • 同步代码先执行
  • 异步代码循环执行宏任务,微任务

这个时候 , 我们再来看这段代码的执行顺序 ,简直是有手就行 ~

首先执行同步代码,遇到  setTimeout(() => console.log('H'), 0); , setTimeout  会将其回调函数放入宏任务队列中,暂不执行。

接着调用  async1() ,进入  async1  函数,首先执行  console.log('E'); ,输出  E 。

然后遇到  await async2(); ,先执行  async2  函数, async2  函数中  console.log('G'); ,输出  G 。此时  await  会暂停  async1  函数的执行,将  async1  函数中  await  后面的代码( console.log('F'); )放入微任务队列中。

继续执行同步代码,遇到  new Promise((res) => { console.log('I'); res(); }).then(() => console.log('J')); , Promise  的构造函数中的  console.log('I');  会立即执行,输出  I ,并且  Promise  状态变为  fulfilled ,其  then  回调函数会被放入微任务队列中。

此时同步代码执行完毕,开始处理微任务队列。先执行  async1  函数中  await  后面被放入微任务队列的  console.log('F'); ,输出  F 。

接着执行  Promise  的  then  回调函数  console.log('J'); ,输出  J 。

微任务队列处理完毕后,开始处理宏任务队列,执行  setTimeout  的回调函数  console.log('H'); ,输出  H 。

综上所述,这段代码的执行顺序是: E 、 G 、 I 、 F 、 J 、 H 。

总结

两个点

  • 顺序 :同步代码 => 【微任务,宏任务】(循环)
  • 循环 :宏任务 => 微任务 , 没有微任务继续执行宏任务
    • image.png