让你快速拿捏大厂面试中关于eventloop执行顺序问题

205 阅读4分钟

前言

都说春招最好的时期就是金三银四这几个月,笔者最近也是在疯狂的投简历、面试、复盘中,在这个过程中也是遇到了很多问题,发现了自己很多的不足之处,笔者在面试过程中发现很多公司都非常喜欢考关于eventloop执行顺序相关的问题。下面笔者将用这篇文章详细讲清eventloop中宏任务和微任务的执行顺序问题,让各位jym在面试碰到这类题目时能够侃侃而谈,直接惊艳面试官😎。

进程和线程

先简单聊一下进程和线程吧,进程是操作系统分配资源的最小单位,它表示一个正在运行的一个程序的实例。而每个进程都有自己的地址,这里面会包含代码、堆、栈及线程等资源。

线程是CPU调度的最小单位,是进程中的一个实体,每个线程都有自己的调用栈和作用域 。

而js是一个单线程的脚本语言,这也就意味着在执行一行代码时不会同时去执行另一行代码。

异步

异步编程是一种允许程序在等待某项操作完成的同时继续执行其他任务的编程方法,从而提高效率和响应速度。 在js里通常可以通过回调函数、Promise等机制来实现异步调用。

在异步中,又分为宏任务和微任务,这两个是js中的事件循环机制处理的两种不同类型的任务队列,其中宏任务包括整体代码块、I/O、UI渲染等,而微任务则包含Promise回调、MutationObserver等,微任务在每次执行完宏任务后优先执行。

Event Loop

js的事件循环(event loop)是一种机制,它负责执行异步代码并管理调用栈与任务队列之间的交互,确保异步操作如定时器、 promises 和 I/O 操作能够在适当的时候被执行。简而言之,事件循环使得 js能够处理并发操作,而无需多线程。

通常来讲,event loop的执行顺序如下所示:

image.png

注意了,这里最重要的就是循环二字,event loop 的核心就在有他是一个不断运行的循环,始终监听任务队列中的新任务,这种持续性确保了程序能够实时响应外部或内部发生的各种情况。

下面一起来看这段代码来更好的理解event loop 的执行顺序吧,这道题目是笔者一个哥们前两天面B站时碰到的题目,笔者认为这道题目还算比较有代表性的

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

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

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

jym可以先自己想一下这道题目的答案是什么,再来看笔者的解释。

首先,async1()函数被调用,这导致立即输出'E'。接下来,遇到await async2();,控制权转交给async2()函数,它执行并输出'G'。注意,虽然async2()是一个异步函数,但它的执行是同步进行的,直到遇到可能存在的异步操作或await关键字。所以说console.log('F')就先被加入到微任务队列里了。

与此同时,setTimeout(() => console.log('H'), 0);被注册为一个宏任务,这意味着它的回调会被放入宏任务队列中,并且只有在当前的任务(包括微任务)全部完成后才会被执行。

然后,创建了一个新的Promise实例,其构造器内的代码是同步执行的,因此会立即输出'I'。尽管这个Promise很快完成并且.then()方法被调用,但是该回调不会立即执行,而是被放置在微任务队列中等待执行。

在当前宏任务结束后,微任务队列中的所有任务将被依次执行,首先是async1()await之后的代码,输出'F',接着是Promise.then()回调,输出'J'。

最后,当所有的微任务都处理完毕后,事件循环开始处理宏任务队列中的任务。在这个例子中,就是那个由setTimeout设置的回调,最终输出'H'。

所以,这段代码的输出顺序为E G I F J H,