彻底搞清楚Event Loop

122 阅读5分钟

前言

创业完全找不到任何出路了,春节又有点遥远,还是得着手准备找工作了😭;

准备面试,顺手写点总结的文章,在梳理思路的同时希望也能为大家提供一点帮助吧。

ps:这个系列会不定期更新

正文

理解起来其实也很简单,Event loop 叫做事件循环,重点是“循环” 二字,对于理解代码执行非常重要。

Event Loop 是什么?为什么?

简单来说就是一句话,因为JS 是单线程的,且自上而下执行,Event Loop 就是用来解决异步事件执行顺序的问题。

当JS 代码执行的时候,会创建一个调用栈,所有JS 代码都在这个调用栈里执行,同时会创建一个宏任务队列和一个微任务队列。

事件循环开始

先检查调用栈是否为空,如果不为空则执行调用栈里的代码;如果为空,则从宏任务队列中取出一个任务并执行。

宏任务

整个js 代码块就是一个宏任务,进入函数体,如果在这个宏任务中创建了微任务,则将微任务加入微任务队列,切记,是加入队列,并没有执行。

常见宏任务:整体代码块、定时器

微任务

宏任务执行完毕后,如果微任务队列不为空,则从微任务队列中取出一个微任务,并执行,这个过程会一直执行,直到微任务队列为空。

常见微任务:promise.then/catch/finally

基本概念概述完毕,

例子一

先看一个简单的例子,解析如果没写明白,请评论区告知。

console.log(1); // 第一次循环宏任务

// 定时器,进入宏任务队列
setTimeout(() => {
  console.log(2);
});

// 声明函数
function fn() {
  console.log(3);
  // 返回promise
  return new Promise((resolve) => {
    console.log(4);
    resolve();
  });
}

// 执行函数fn,并且调用promise 的回调函数
// .then,进入微任务队列
fn().then(() => {
  console.log(5);
});

理解关键点:

  1. prmise 的主体是同步的,只有回调函数才是异步的微任务;
  2. 如果宏任务中创建了微任务,会加入微任务队列;

执行过程:

  1. 事件循环开始:检查调用栈,如果为空,则从宏任务队列中取出一个任务,并执行。所以第一次获取到的宏任务输出结果就是1 3 4;宏任务队列中有一个定时器,微任务队列中有一个promise 的回调函数;
  2. 第二次循环,第一步执行完毕了这一次循环的宏任务,然后先检查微任务,直到微任务队列为空;输出结果5
  3. 继续循环,先获取宏任务,所以输出2
  4. 直到调用栈为空。

此时再看执行过程是不是很明了?

例子二

将第一个例子改的复杂一点

console.log(1);

// 定时器一
setTimeout(() => {
  console.log(2);
});

function fn() {
  console.log(3);
  return new Promise((resolve) => {
    console.log(4);
    resolve();
  });
}

// 定时器二
setTimeout(() => {
  console.log(8);
  // promise 回调一
  fn().then(() => {
    console.log(5);
    // promise 回调二
    fn().then(() => {
      console.log(6);
      // 定时器三
      setTimeout(() => {
        console.log(7);
         // promise 回调3
        fn().then(() => {
          console.log(8);
        });
      });
    });
  });
});

执行过程:

  1. 事件循环开始,调用栈如果为空,则取一个宏任务,当前循环的宏任务就是整体代码块。在这一次循环当中,执行宏任务,输出1 2 8;
  2. 在定时器二 这个宏任务 中 创建了一个 微任务(promise 回调一),那么将这个微任务加入到微任务队列。
  3. 宏任务执行完毕后,检查微任务队列,此时微任务队列中只有一个微任务就是 promise 回调一,执行回调,输出 3 4 5,微任务,微任务中又加入了一个微任务(promise 回调二),输出3 4 6,在执行promise 回调二 的时候,又创建了一个宏任务。
  4. 再次循环,执行宏任务输出7,并且加入了微任务 promise 回调三 ,输出8
  5. 所以最后输出顺序就是:
    1. 1
    2. 2 8
    3. 3 4 5
    4. 3 4 6
    5. 7
    6. 3 4 8

补充说明

还有一个高频问题,宏任务和微任务的优先级,我认为宏任务是优先于微任务的,因为当一次事件循环执行的时候,会先取出一个宏任务,并执行,如果当前这一次的循环创建了一个微任务,仅仅是将微任务加入到微任务队列中,并没有执行,只有当宏任务执行完毕之后,并且微任务队列不为空,那么才会执行微任务,我不理解为什么这么多人说微任务是先与宏任务执行的,这样子似乎搞错了循环这个概念。

拿例子一 来说为什么宏任务是先与微任务执行的。

事件第一次循环:

调用栈为空,则取一个宏任务放入调用栈,执行三个红色框的代码,fn().then 是一个微任务,仅仅是加入微任务队列,没有执行。宏任务执行完毕之后再检查微任务队列,然后将本次循环中的微任务全部执行完。此时调用栈为空。

事件第二次循环:

继续取出宏任务,此时宏任务只有蓝色框的定时器,所以执行定时器的内容。此时微任务队列为空,本次循环执行完毕。

将事件循环拆解开来,按照每一次循环来理解,宏任的优先级就是高于微任务。