大厂面试最爱问的Event Loop,一次性弄懂它!(绝密,含字节真题)

大厂面试最爱问的Event Loop,一次性弄懂它!(绝密,含字节真题)

按照惯例,还是要先强调下本文绝密,因为含有字节真题 :)

缘起

最近在准备换工作,也大概看了下平时掌握不深的 JavaScript 事件循环机制。

但是在今天字节面的笔试中,还是扫到了我的知识盲区。

先看题,输出以下代码的打印结果:

async function async1() {
    console.log('1');
    await async2();
    console.log('2');
}

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

console.log('4');
async1();

setTimeout(() => {
    console.log('5');
}, 0)

new Promise((resolve, reject) => {
    console.log('6');
    resolve();
}).then(() => {
    console.log('7');
})

console.log('8');

看到这里你可能已经蠢蠢欲动了。想必你内心已经有了你的答案,此时你不妨先去浏览器控制台打印一下,验证你的结果。

如果你全对,那么恭喜你,这块已经难不倒你了,你可以直接跳过本文;如果你像我一样,谨小慎微还是错了一两个地方,那么继续往下看是很有必要的。

「玩归玩,闹过闹,别拿学习开玩笑」。

我收回刚才的玩笑话,做对的同学也快回来吧!这道题目看起来平平无奇,但潜藏的知识点可不少。跟我一起深挖探究下,多学一点总归是好的,也看看你是否想得都对🤣。

事件循环的背景:同步 & 异步

众所周知,JavaScript是一种单线程语言,即同一时间只能做一件事情。因为它设计之初主要就是为了用来与用户互动,以及操作DOM的。

为了防止 协调事件、用户交互、脚本执行、UI 渲染和网络处理等行为造成 JS 单线程阻塞,于是将不少任务作为异步去执行处理。

我们前端经常会提两个概念:同步异步

同步任务

同步任务很好理解。如果一段代码,一执行马上就能得到期望结果,那么这个代码就是同步任务。

由于JS是单线程的,所以同步任务都是排队执行的,只有当一个同步任务执行完毕,才能执行下一个同步任务。

异步任务

如果一段代码,在执行时还不能够得到预期结果,而是需要在将来通过一定的手段拿到,那么这块代码就是异步任务。

上面已经提到,异步任务的设计是为了解决 JS 单线程阻塞的问题。

大厂面试最爱问的Event Loop,一次性弄懂它!(绝密,含字节真题)

异步任务的实现:不进入JS单线程,而是放在任务队列中。若有多个异步任务则需要在任务队列中排队等待。任务队列类似于缓冲区,任务下一步会被移到执行栈然后JS线程执行调用栈的任务。

事件循环的基础:宏任务 & 微任务

异步任务中,有的是 setTimeout 这种耗时很久的,有的是 promise 这种耗时较短的。

当异步任务很多的时候,耗时久的就会阻塞后面所有的异步任务,包括一些很快可以执行完的也被阻塞。

于是 JS 引擎就将异步任务分类管理,划分成两个队列:宏任务队列微任务队列

宏任务有:

  • <script>标签中的运行代码
  • setTimeout、setInterval的回调函数
  • 事件触发的回调函数,例如DOM EventsI/OrequestAnimationFrame、Ajax、UI交互等

微任务有:

  • Promise的回调函数:then、catch、finally
  • MutationObserver:使用方式
  • queueMicrotask:使用方式
  • process.nextTick:Node独有

事件循环的原理:宏任务与微任务交替执行

为了既不阻塞JS单线程的执行,同时又保障JS执行的效率,前辈们设计了出了一套事件循环机制

先执行一个宏任务(document 下 script 标签中的所有同步代码),执行过程中如果产出新的宏/微任务,就将他们推入相应的任务队列,之后再执行微任务队列,再之后就执行宏任务队列,如此循环。以上不断重复的过程就叫做 Event Loop(事件循环)

大厂面试最爱问的Event Loop,一次性弄懂它!(绝密,含字节真题)

回到开始的题目

有了上面的知识储备,我们再看开头的题目就不显得那么迷糊了。

一起来再仔细盘下执行过程:

  • 所有的代码执行会被 js 引擎当成一个宏任务,但不会推到宏任务队列
  • 首先打印 4,因为 async1async2 两个函数开始只是声明,没有调用
  • 然后就是打印 1,看到 async 不要慌,async函数里的内容大都是同步执行的(除了 await 后面是作为 promise 来执行的)
  • 然后就是打印 3,没得讲,调用函数 async2,里面没有 await
  • 然后就是打印 6,别说是 2,打印 2 这个任务在 await 后面,是会被推进了微任务队列的
  • 为啥是 6 解释下,Promise的函数体是同步执行的,只有 then、catch、finally 这些回调才是异步调用的(见上文)
  • 此时的回调函数打印 7,被推进了微任务队列,异步执行
  • 然后就是打印 8,毫无疑问是同步执行
  • 同步任务至此已经全部完成,宏任务执行完了便开始执行微任务队列的微任务了
  • 首先是打印 2,它最开始被推进微任务队列
  • 其次是打印 7,初始化 promise 的时候将它推进微任务队列的(因为回调是异步执行的),这个时候微任务队列也已经执行完
  • 最后是打印 5,根据前面所讲 setTimeout 是一个标准的宏任务,微任务队列执行完了就要执行宏任务队列

这里有几个重点需要关注下:

  • 同步任务会立即执行,只有异步任务才会被加入到任务队列中进行事件循环(Event Loop)执行
  • 宏任务和微任务都是异步任务下的产物,同步任务是立即执行的,所以它既不存在宏任务队列,也不存在微任务队列
  • 很多人说微任务总是优先于宏任务执行,这句话不严谨。只有在事件循环中,微任务总是优先于宏任务执行(因为整个脚本的执行就是一个宏任务。只有在这个宏任务执行完毕后,JavaScript 引擎才会去执行微任务队列中的所有微任务)
  • new Promise 中的代码是同步的,但是回调函数则是异步的(微任务)
  • async/await 是 JavaScript 中使用同步代码来处理异步的一种方式,它本身并不是宏任务或微任务。但 async 函数将返回一个 Promise 对象,即 await 后面的代码会作为 Promise 的回调来处理。因此 await 后面的代码 会被当成微任务,加入到微任务队列中的
  • setTimeout() 的第2个参数是为了告诉 JavaScript 再过多长时间把当前任务添加到宏任务队列中

缘灭

因为下份工作不想太卷,所以本来也没打算冲刺字节的,甚至说是对字节这个面试毫无准备。

但不知道是哪位猎头不讲武德,偷偷摸摸给我投了。

我只能说,大意了,没有闪!

万万没想到简历还过了,我就当顺便练练手,为后续面试积累下经验吧!

看到这里,我奉劝各位还是忘了这道题目吧!

只要「真题穿肠过,知识心中留」就可以了。

我不想变成明天掘金首页字节真题泄密的猪脚,不想被字节请去喝茶,更不想去字节面第二轮。

掘友们,希望你们耗子尾汁,不要搞窝里斗!

大厂面试最爱问的Event Loop,一次性弄懂它!(绝密,含字节真题)

原文链接:https://juejin.cn/post/7229372047414902839 作者:敲完代码再睡觉

(0)
上一篇 2023年5月5日 上午10:11
下一篇 2023年5月5日 上午10:21

相关推荐

发表回复

登录后才能评论