JavaScript 事件循环

0x01 事件循环

  • JavaScript 是单线程的,即同一时间内仅能执行一个操作

  • 在单线程中,当一段代码中存在需要等待或触发的任务时,会阻塞线程,影响后续代码的执行,因此需要将代码分为同步异步,其执行过程如下:

    flowchart LR 代码--同步-->JS引擎-->执行栈--立即执行-->执行栈 代码--异步-->s[宿主环境<br/>如浏览器或NodeJS]--触发事件-->任务队列 执行栈--查看是否有异步任务-->任务队列--获取异步任务-->执行栈

    异步编程方法详见《JavaScript 异步编程 | 博客园-SRIGT》

  • 在上述执行过程中,执行栈反复向任务队列查看并获取任务的过程,称为事件循环

0x02 宏任务与微任务

  • JavaScript 的异步任务又分为宏任务微任务
    • 宏任务由宿主环境发起,如 AjaxfetchsetTimeoutsetInterval<script>、事件等
    • 微任务由 JS 引擎发起,如 async/await Object.observeprocess.nextTickPromisethencatch 回调方法等
  • 任务队列分为宏任务队列和微任务队列
  • 在事件循环中,优先执行所有微任务,之后执行宏任务

0x03 requestAnimationFrame

  • 在事件循环中,分为执行渲染两步

    • 对于以下代码,事实上仅最后一句有效

      button.addEventListener("click", () => {
        div.style.display = "none";
        div.style.display = "block";
        div.style.display = "none";
        div.style.display = "block";
        div.style.display = "none";
      });
      

      因为所有逻辑代码都执行后才会进入渲染,而非每次变化都会进行渲染

  • 所有同步和异步代码均在执行阶段

    • 如同步代码 while(true){} 在执行阶段会阻塞后续渲染,而递归则不会阻塞
  • Chromium 提供的 requestAnimationFrame(简称 rAF)方法允许代码在渲染阶段的渲染前执行

  • 举例:方块先右移 1000px,再左移 500px

    1. 页面元素与样式

      <body>
        <div style="width: 100px; height: 100px; background-color: black;"></div>
        <div style="width: 500px; height: 5px; background-color: aqua;"></div>
        <button>开始</button>
        <script>
          // JavaScript 逻辑代码
        </script>
      </body>
      
    2. JavaScript 逻辑代码

      const div = document.querySelector("div");
      const button = document.querySelector("button");
      button.addEventListener("click", () => {
        div.style.transform = "translateX(1000px)";
        div.style.transition = "transform 2s ease-in-out";
      
        requestAnimationFrame(() => {
          div.style.transform = "translateX(500px)";
        });
      });
      

      此时方块只会右移 500px,并不符合要求
      因为完成执行进入渲染时,先执行 rAF,后渲染,因此此时的代码相当于:

      const div = document.querySelector("div");
      const button = document.querySelector("button");
      button.addEventListener("click", () => {
        div.style.transform = "translateX(1000px)";
        div.style.transition = "transform 2s ease-in-out";
        div.style.transform = "translateX(500px)";
      });
      
    3. 解决方法:

      const div = document.querySelector("div");
      const button = document.querySelector("button");
      button.addEventListener("click", () => {
        div.style.transform = "translateX(1000px)";
        requestAnimationFrame(() => {
          requestAnimationFrame(() => {
            div.style.transition = "transform 2s ease-in-out";
            div.style.transform = "translateX(500px)";
          });
        });
      });
      

-End-

posted @ 2024-09-09 15:37  SRIGT  阅读(9)  评论(0编辑  收藏  举报