面试必备JavaScript事件循环机制(event loop)_前端

用简单的流程解释事件循环。

在了解什么是事件循环之前我们需要先行了解 javascript是一个单线程语言 和 javascript的事件分类

javascript是一个单线程语言

什么是单线程。举个例子:这就好像食堂打饭,如果食堂开设多个窗口,那么就可以有多个同学同时打饭。但如果食堂只有一个窗口,那么所有的同学都要在这一个窗口后面一个个排队打饭。单线程,就属于后者这个情况。同理,javascript中的所有任务都只有一条线程在处理。

面试必备JavaScript事件循环机制(event loop)_vue.js_02

显然,这种机制存在很大的弊端。如果我们有一个任务卡死了。那么后面所有的任务都无法被执行。或者有某个任务耗时很长,那么就会导致后面所有的任务都被延迟执行。在这样的环境下,javascript诞生了两个任务种类:同步任务和异步任务

javascript的任务分类

接上文,javascript中的所有任务被分为同步任务和异步任务两大类。

同步任务介绍:就是只要被扫描到,就可以被主线程马上执行的任务。(优先于所有异步任务)

异步任务介绍:即使被扫描到,也不会马上执行,异步任务会被压入异步任务队列中,等待主线程中的任务全部清空了,再被召唤执行。

常见的异步任务有如下几种

  1. Promise.then() ---微任务
  2. async/await ---Promise的语法糖 ---微任务
  3. setTimeout() ---宏任务
  4. setInterval() ---宏任务
  5. ...(还有更多,不常见的)

举个例子

setTimeout(() => {
console.log("1");
},0)

console.log(2)
复制代码

以上的输出结果是 2,1。是的,虽然setTimeout的延迟是0,但setTimeout是一个异步任务,他一定会在所有同步任务执行完毕之后再去执行。

宏任务与微任务

javascript的异步任务又被分为宏任务和微任务。

在异步任务中,有些异步任务的平均执行周期很长,这些任务被javascript标记为宏任务(比如setTimeout())。而平均执行周期相对比较短的任务,被javascript标记为微任务(比如promise.then())。

而宏任务和微任务在执行顺序上是不一样的。具体执行机制如下。

(这一块有点绕,是重点,细看👇)

当有异步任务被压入异步任务队列时候,javascript会将这些异步任务分为宏任务和微任务两个新的队列。然后,在所有同步任务执行完毕之后,异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个(注意是一个)宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行一个宏任务,以此循环。这一套流程,就是事件循环(event loop)

图例👇

面试必备JavaScript事件循环机制(event loop)_javascript_03

例题详解

先来个简单的

第一题

例题如下↓ 问:输出结果

setTimeout(() => { console.log("1") }, 0);  //异步任务 - 宏任务

        console.log(2);   //同步任务

        Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务

         console.log(6);   //同步任务
复制代码

输出结果:2 6 3 1

面试必备JavaScript事件循环机制(event loop)_面试_04

解析参考:

首先,同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。

同步任务执行完毕后,还剩下一个宏任务和一个微任务。

微任务优先于宏任务执行,所以先输出 3 再输出 1

得答案:2 6 3 1

第二题

在上一题的基础上增加亿点点难度

面试必备JavaScript事件循环机制(event loop)_前端_05

例题如下↓ 问:输出结果

//第一个宏任务
         setTimeout(() => {
              console.log(1); //宏任务中的同步任务
              Promise.resolve().then(() => { console.log(7) }) //宏任务中的微任务
         }, 0);  //异步任务 - 宏任务

        console.log(2);   //同步任务

        Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务

        //第二个宏任务
        setTimeout(() => { 
          console.log(8); //宏任务中的同步任务
          setTimeout(() => { console.log(5) }, 0)      //宏任务中的宏任务 第四个宏任务
        }, 0);

        //第三个宏任务
        setTimeout(() => { 
          Promise.resolve().then(() => { console.log(4) })  //宏任务中的微任务
        }, 0);
        
         console.log(6);   //同步任务
复制代码

答案:2 6 3 1 7 8 4 5

面试必备JavaScript事件循环机制(event loop)_vue.js_06

解析参考:

首先,同步任务必定优先于所有所有异步任务并按顺序执行。所以输出 2 6。

然后同一批次中剩下一个微任务和一个三个宏任务。

因为宏任务必定会在同一批次环境中的微任务全部执行完毕后再执行,所以场上当前批次中唯一一个微任务先执行。输出3

还剩下三个宏任务。执行第一个宏任务,宏任务中有一个同步任务和一个异步任务。这里要注意两点。

  1. 统一批次宏任务中按顺序执行
  2. 一次只执行一个宏任务,然后同步任务当场执行。微任务压入队列。然后就要去检查有没有微任务,有则执行

所以,第一个宏任务执行的时候,产生了一个同步任务和一个微任务。需要注意,宏任务一次只执行一个。执行完之后发现同步任务当场执行(输出1),然后查看微任务队列中有没有微任务可以执行。发现有,则执行微任务(输出7)

然后,才开始执行第二个宏任务。执行第二个宏任务产生了一个同步任务,同步任务当场执行(输出8),产生一个宏任务(宏任务压入红任务执行队列,也就是所有宏任务之后),按事件循环,再次检查是否存在未执行的微任务,发现没有,不执行。

然后执行第三个宏任务,第三个宏任务中产生一个微任务,按事件循环,再去寻找是否存在未执行的微任务,发现有,则执行(输出4)

最后执行第四个宏任务(第二个宏任务产生的)。走一遍事件循环的流程,输出5

END。

个人认为最需要注意的细节是,事件循环每一次只执行一个宏任务

面试必备JavaScript事件循环机制(event loop)_javascript_07