JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

本文主要讲解关于JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的相关内容,让我们来一起学习下吧!

引言

众所周知,JavaScript是一门单线程语言,它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
但是渲染主线程承担着诸多工作如:渲染页面等。

如果所有代码都是同步的话,如果一个代码卡住了,就会导致主线程代码的阻塞,从而导致运行性能效率受到极大的影响。

所以浏览器采用了异步的方式,来避免这种情况的发生。具体是当某些任务发生时,如计时器、网络、事件监听等,主线程将任务交给其他线程去计时或监听,当主线程无事可做时或计时监听触发时,将任务加入到消息队列的末尾排队,等待主线程调度。

宏任务与微任务

过去的JavaScript中,异步任务又分为宏任务微任务

JavaScript 引擎遵循事件循环的机制,在执行完当前宏任务后,会检查微任务队列,执行其中的微任务,然后再取下一个宏任务执行。这个过程不断循环,形成事件循环。

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

1、宏任务(Macrotasks)是一些较大粒度的任务,包括:

  • 所有同步任务
  • I/O操作,如文件读写、数据库数据读写等
  • setTimeoutsetInterval
  • setImmediate(Node.js环境)
  • requestAnimationFrame
  • 事件监听回调函数等
  • ...

2、微任务(Microtasks)是一些较小粒度、高优先级的任务,包括:

  • Promisethencatchfinally
  • async/await中的代码
  • Generator函数
  • MutationObserver
  • process.nextTick(Node.js 环境)
  • ...

事件循环(Event Loop)

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。

在Chrome中,它开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入队列末尾。

过去把消息队列简单分为宏任务微任务,但是随着浏览器复杂度急剧提升,W3C已经不再使用宏任务的说法。

根据W3C的最新解释:

  1. 每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务分属于不同的队列,在一次事件循环中,浏览器可以根据实际情况从不同队列中取出任务执行。
    大概有以下几个的队列DOM操作用户交互网络请求网页导航和历史记录遍历渲染
    查看原文

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

  1. 同时浏览器必须准备好一个微队列,微队列中的任务优先于所有其他任务执行。查看原文

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

在目前的Chrome实现中,至少包含了以下队列:

队列类型 描述 优先级
微队列 用于存放需要最快执行的任务 最高
交互队列 用于存放用户操作后产生的事件处理任务
延时队列 用于存放计时器到达后的回调任务
网络队列 用于处理网络活动产生的任务

此处的优先级仅方便于我们对于谷歌浏览器队列的实现的方便展现,并未在W3C中强约束优先级,所以每个浏览器的实现方式可能些许不同

演示

那有人就会问了,如果是按照这个说法,很多关于消息队列的面试题岂不是会混乱,这么大的事情应该大家都知道吧,在实际开发中,有什么区别呢,我们上代码试试。

  1. 首先,我们需要一个阻塞主线程的函数
    // delay函数 传入一个时间,使用循环,阻塞主线程的运行,方便我们后续的操作
    function delay(duration) {
      const now = Date.now();
      while (Date.now() - now < duration) { }
    }
  1. 网络队列,在网络队列中,我们通过调用fetch然后加上delay函数,让函数进入队列。
    function addNetWork() {
      fetch('./1.json').then((res) => {
        console.log('网络队列执行');
      })
      console.log('添加网络队列');
      delay(2000)
    }
  1. 延时队列,我们使用setTimeout函数加上delay,延时为0,让函数进入队列。
  function addDelay() {
      setTimeout(() => {
        console.log('延时队列执行');
      }, 0)
      console.log('添加延时队列');
      delay(2000)
    }
  1. 交互队列,我们给一个按钮绑定onclick事件,然后再绑定事件后,点击按钮,让函数进入队列。
    function addInteraction() {
      interaction.onclick = function () {
        console.log('交互队列执行');
      }
      console.log('添加交互队列');
      delay(2000)
    }
  1. 为了方便,我们需要一个主函数去触发上面三个方法。
    begin.onclick = function () {
      addDelay()
      addNetWork()
      addInteraction()
      console.log('添加成功');
    }

因为node和浏览器实现事件循环的方式不同,所以我们使用谷歌浏览器(v123.0.6312.106)测试

完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<button id="begin">开始</button>
<button id="interaction">添加交互任务</button>

<body>
  <script>
    const begin = document.querySelector("#begin");
    const interaction = document.querySelector("#interaction");
    // delay函数 传入一个时间,使用循环,阻塞主线程的运行,方便我们后续的操作
    function delay(duration) {
      const now = Date.now();
      while (Date.now() - now < duration) { }
    }

    function addDelay() {
      setTimeout(() => {
        console.log('延时队列执行');
      }, 0)
      console.log('添加延时队列');
      delay(2000)
    }

    function addNetWork() {
      fetch('./1.json').then((res) => {
        console.log('网络队列执行');
      })
      console.log('添加网络队列');
      delay(2000)
    }

    function addInteraction() {
      interaction.onclick = function () {
        console.log('交互队列执行');
      }
      console.log('添加交互队列');
      delay(2000)
    }

    begin.onclick = function () {
      addDelay()
      addNetWork()
      addInteraction()
      console.log('添加成功');
    }

  </script>
</body>
</html>

分析

此时,我们使用宏任务与微任务的方式对我们的队列进行分析

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

预期的打印结果是

  • 添加延时队列
  • 添加网络队列
  • 添加交互队列
  • 添加成功
  • 延时队列执行
  • 网络队列执行
  • 交互队列执行

但是实际运行结果如下(注意:在控制台打印添加交互队列时,点击了添加交互任务按钮)

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

我们发现与我们之前预期的宏任务微任务结果不一致,交互队列执行代码早于延时与网络队列。所以可以证明,浏览器对于不同类型的队列,有不同的处理时机。这里预测是因为浏览器希望用户点击后,可以尽快响应,而不用等待延时或者网络等队列的执行。所以也可以证明谷歌浏览器实现已经不是简单的宏任务与微任务了。

新的分析图应该为:

JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的

拓展

那我们还可以提出问题,那网络队列和延时队列之间呢?

我们交换一下启动函数的代码顺序:

    begin.onclick = function () {
      addNetWork()
      addDelay()
      addInteraction()
      console.log('添加成功');
    }

此时控制台变为:

  • 添加网络队列
  • 添加延时队列
  • 添加交互队列
  • 添加成功
  • 交互队列执行
  • 网络队列执行
  • 延时队列执行

我们可以看到,网络和延时队列的执行还是按照宏任务的方式,谁先进入队列,谁先运行。他们的优先级是相同的。

那微队列有没有变化呢?

我们修改一下代码,在我们运行队列时添加一个微队列,完整代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<button id="begin">开始</button>
<button id="interaction">添加交互任务</button>

<body>
  <script>
    const begin = document.querySelector("#begin");
    const interaction = document.querySelector("#interaction");
    // delay函数 传入一个时间,使用循环,阻塞主线程的运行,方便我们后续的操作
    function delay(duration) {
      const now = Date.now();
      while (Date.now() - now < duration) { }
    }
    // 创建一个微队列任务
    function microTask() {
      Promise.resolve().then(() => {
        console.log('微队列执行');
      })
    }

    function addDelay() {
      setTimeout(() => {
        console.log('延时队列执行');
        microTask()
      }, 0)
      console.log('添加延时队列');
      delay(2000)
    }

    function addNetWork() {
      fetch('./1.json').then((res) => {
        console.log('网络队列执行');
        microTask()
      })
      console.log('添加网络队列');
      delay(2000)
    }

    function addInteraction() {
      interaction.onclick = function () {
        console.log('交互队列执行');
        microTask()
      }
      console.log('添加交互队列');
      delay(2000)
    }

    begin.onclick = function () {
      addNetWork()
      addDelay()
      addInteraction()
      console.log('添加成功');
    }

  </script>
</body>
</html>

执行结果是:

  • 添加网络队列
  • 添加延时队列
  • 添加交互队列
  • 添加成功
  • 交互队列执行
  • 微队列执行
  • 网络队列执行
  • 微队列执行
  • 延时队列执行
  • 微队列执行

我们可以看到对于微队列的执行是没有变化的,当微队列有事件时,会先执行微队列中的事件。

结论

W3C中对于事件循环(Event Loop)的处理已经不再是之前的宏任务微任务的方式了,每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务分属于不同的队列,在一次事件循环中,浏览器可以根据实际情况从不同队列中取出任务执行。但是浏览器也必须准备好一个微队列(microtask),微队列中的任务优先于所有其他任务执行。

写在最后

以上内容是通过查阅W3C文档和参考了渡一的前端大师课,只测试了谷歌浏览器的实现,感兴趣的可以试试其他浏览器的实现。部分概念引用了掘金其他关于事件循环的文章,链接如下:

  • # JavaScript事件循环机制(event loop)
  • # JS的执行原理,一文了解Event Loop事件循环、微任务、宏任务

本人刚进入前端行业,对于文章内容有问题的可以交流评论。有错误也请指正。 感恩~

(大三正在找实习,有兴趣的可以联系我~)

以上就是关于JS事件循环(Event Loop)改了?宏任务不存在了?让我们来看看最新的是什么样的相关的全部内容,希望对你有帮助。欢迎持续关注程序员导航网,学习愉快哦!

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...