likes
comments
collection
share

前端进阶:看完就清楚的 JS EvenetLoop(事件循环)

作者站长头像
站长
· 阅读数 18

准备

我们都知道,JS 是单线程的,需要执行完当前任务后,才能执行下一个任务。这就类似于你打饭,前面一个人打了饭,后面的人才能打。不然前面的人在那卡 bug,你后面的人就被阻塞打不聊饭,就只有饿肚子。

同理,在 JS 主线程的执行栈上,也是需要一个任务一个任务的执行。如果遇到计算量大、耗时久的任务,那么后续的任务就会被阻塞,造成响应不及时、页面卡顿等问题。因此,JS 有了它独特的生态:EventLoop(事件循环)

JS 任务分类

JS 的任务分为同步任务、异步任务,而异步任务又分为宏任务、微任务

比如:

  • Promise.then():微任务
  • setTimeOut:宏任务
  • async、await:微任务
  • setInterval:宏任务
  • ...(还有很多不常用的)

那么他们是如何执行的呢?

EventLoop

EventLoop 流程

EventLoop 的流程可以简单来说可以概况为:同步任务 ➡ 微任务 ➡ 宏任务

具体而言:

  1. 执行同步任务
  2. 同步任务执行完毕后,去微任务队列,看有没有需要执行的微任务
  • 如果有,则把当前微任务队列里面所有的微任务推入主线程的执行栈,执行微任务,执行完毕,去宏任务队列。
  • 如果没有,则去宏任务队列
  1. 看有没有需要执行的宏任务,如果有,则推入主线程执行栈执行(只取一个宏任务)。
  2. 当前这个宏任务执行完后,又去微任务队列,看有没有需要执行的微任务。如果有,全部推入执行栈。执行完毕后,又回到宏任务队列,取出当前一个宏任务,推入执行栈执行。执行完又去微任务队列,依次循环。

图解如下:

前端进阶:看完就清楚的 JS EvenetLoop(事件循环)

例题讲解

直接看代码,很快就懂了

第一题:

console.log(1)

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

Promise.resolve().then(() => {
  console.log(3)
})

console.log(4)

输出顺序: 1 4 3 2

分析:

  1. JS 按顺序执行同步任务,所以先执行 console.log(1)console.log(4)
  2. 执行完同步任务,看微任务队列,有没有需要执行的微任务,此时微任务队列只有一个微任务 Promise.resolve().then(() => console.log(3)),所以推入执行栈,执行 console.log(3)
  3. 当前微任务执行完,回到宏任务队列,此时有一个 setTimeout,推入执行栈,执行 console.log(2)(注意:每次去宏任务队列时,都只取一个宏任务出来

第二题:

//S1
setTimeout(() => {
  console.log(1); 
  //S4
  setTimeout(() => {
    //M2
    Promise.resolve().then(() => {
      console.log(9);
    }); 
  }, 0);
  //M3
  Promise.resolve().then(() => {
    console.log(7);
  }); 
}, 0); 

console.log(2); 

//M1
Promise.resolve().then(() => {
  console.log(3);
}); 

//S2
setTimeout(() => {
  console.log(8);
  //S5
  setTimeout(() => {
    console.log(5);
  }, 0); 
}, 0);

//S3
setTimeout(() => {
  //M6
  Promise.resolve().then(() => {
    console.log(4);
  }); 
}, 0);

console.log(6); 

输出顺序: 2 6 3 1 7 8 4 9 5

分析: 为了方便我们分析,我给每个宏任务取个别名 S几,微任务取别名 M几

  1. 首先,执行所有的同步任务,也就是 console.log(2)console.log(6),所以先打印 2 6
  2. 宏任务和微任务依次进入各自的队列。此时的宏任务队列、微任务队列分别是:
  • 宏任务队列:S1、S2、S3

  • 微任务队列:M1

    那么此时,取出所有的微任务,也就是 M1,执行 console.log(3)

    然后去宏任务队列,取出一个宏任务(队列先进先出,所以先取出 S1),执行 console.log(1),执行的过程中又遇到了 S4,此时 S1 还没执行完,那么 S4 入宏任务队列,然后遇到 M3,执行 console.log(7),此时 S1 执行完毕。此时打印的结果为:2 6 3 1 7

  1. 执行完前面两步后,宏任务队列、微任务队列分别是:
  • 宏任务队列:S2、S3、S4

  • 微任务队列:

    此时,微任务队列没有微任务,那么从宏任务队列取出 S2,执行 console.log(8),然后遇到 S5,此时 S2 还没执行完,所以 S5 推入宏任务队列。此时打印的结果为:2 6 3 1 7 8

  1. 经过第三步后,宏任务队列、微任务队列分别是:
  • 宏任务队列:S3、S4、S5

  • 微任务队列:

    同样,微任务队列没有任务,则从宏任务队列中,取出 S3 执行,然后遇到 微任务 M6,M6 推入微任务队列。

    此时宏任务队列、微任务队列分别是:

    • 宏任务队列:S4、S5
    • 微任务队列:M6

    那同样的,取出 M6,执行 console.log(4),此时打印结果是:2 6 3 1 7 8 4

  1. 经过第四步后,此时宏任务队列、微任务队列分别是:
  • 宏任务队列:S4、S5
  • 微任务队列:

取出 S4,执行 S4,遇到 M2,M2 入微任务队列。

  1. 此时宏任务队列、微任务队列分别是:
  • 宏任务队列:S5
  • 微任务队列:M2

取出 M2,执行 console.log(9),此时打印结果是:2 6 3 1 7 8 4 9

  1. 此时宏任务队列、微任务队列分别是:
  • 宏任务队列:S5
  • 微任务队列:

取出 M5,执行 console.log(5),此时打印结果是:2 6 3 1 7 8 4 9 5

总结

需要注意的点是:每次从宏任务队列里面取宏任务时,只取一个;而从微任务队列里面取微任务时,取全部。

结语

以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论。

转载自:https://juejin.cn/post/7231759334068568123
评论
请登录