前端交互体验优化-空闲时执行计算任务

我们知道,js 是单线程的脚本语言,单线程的特点就是所有代码只能按照线性先后顺序,同步的被执行。

即使我们说 js 可以执行异步任务,但本质是这些异步任务只是放在了等候队列,依然需要等待同步队列执行完成后,才能被加入到同步队列中,同步的去执行。

因此即使将计算任务放到异步队列中,它最终依然会被同步的执行,那么整个计算过程就会阻塞主线程的执行。

比如这样一个场景:点击"开始计算"按钮 执行1000*1000次的打印任务,紧接着点击"计时器"按钮

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>同步执行计算任务</title>
  <style>
    html, body {
      text-align: center;
      height: 100%;
    }
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
    }
  </style>
</head>

<body>

  <button id="clickMe">开始计算</button>
  <br />
  <button id="counter" style="margin-top: 20px;">Counter: 0</button>
  <script>
    const clickBtn = document.getElementById('clickMe');
    const counterBtn = document.getElementById('counter');

    let counter = 0;

    clickBtn.addEventListener('click', () => {
      calc();
    });

    counterBtn.addEventListener('click', () => {
      counter ++;
      console.log(`%cCounter Update: ${counter}`, 'color: green;');
      counterBtn.innerText = `Counter: ${counter}`
    });
    function calc() {
      for (let i = 0; i < 1000; i++) {
        console.log(`%cOuter Calc: ${i}`, 'color: gray;');
        for (let j = 0; j < 1000; j++) {
          console.debug(`${i}-${j}`);
        }
      }
    }
  </script>
</body>

</html>

在执行计算任务的过程中,会发现点击"计时器"按钮的事件长时间得不到响应,整个js线程都被阻塞在执行中的计算任务,也就是我们所说的卡死状态。直到任务计算完成,卡死状态才会结束,事件才会得到响应。

如果真实业务中存在这种大量的计算,且实现方式跟上面一样,可想而知,交互体验将会变的很糟糕!

假如我们的计算任务不能通过算法进行提效,那么应该怎么解决因大量计算导致的交互卡顿或卡死的问题呢?

下面有几种方案可供参考:

  1. 使用 webworker,开启子线程执行计算任务

  2. 使用requestIdleCallback 在主线程中,进行空闲时分片计算

  3. 如果业务可行,将计算任务交给其他端(如:BS架构的服务端)

webworker 虽然可以多线程并发,但是跨线程数据传输的性能损耗和瓶颈,对于数据量大的时候,带来的问题也会很明显。

我们这里主要分析,计算任务必须放在前端处理的场景,所以会采用方案:requestIdleCallback 空闲时分片计算进行举例。

依然是上面的例子,我们使用requestIdleCallback 空闲时接口进行改造

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>空闲时-分片执行计算任务</title>
  <style>
    html, body {
      text-align: center;
      height: 100%;
    }
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
    }
  </style>
</head>

<body>

  <button id="clickMe">开始计算</button>
  <br />
  <button id="counter" style="margin-top: 20px;">Counter: 0</button>
  <script>
    const clickBtn = document.getElementById('clickMe');
    const counterBtn = document.getElementById('counter');

    let counter = 0;

    clickBtn.addEventListener('click', () => {
      calc();
    });

    counterBtn.addEventListener('click', () => {
      counter ++;
      console.log(`%cCounter Update:${counter}`, 'color: green;');
      counterBtn.innerText = `Counter: ${counter}`
    });
    
    function calc() {
      let index = 0;
        
      // 将大的计算任务,拆分为小的计算任务,通过空闲时去分片执行
      function innerCalc(i) {
        console.log(`%cOuter Calc: ${i}`, 'color: gray;');
        for (let j = 0; j < 1000; j++) {
          console.debug(`${i}-${j}`);
        }
      }
      
      function outerCalc(deadline) {
        // 如果有空闲时间,或者计算等待超时,则执行计算任务
        while (index < 1000 && (deadline.timeRemaining() > 0 || deadline.timeout)) {
          innerCalc(index);
          index ++;
        }
        if (index < 1000) {
          requestIdleCallback(outerCalc, {timeout: 1000});
        } else {
          console.log("任务执行结束");
        }
      }
      requestIdleCallback(outerCalc, { timeout: 1000 });
    }
  </script>
</body>

</html>

可以看出来,当我们使用空闲时计算时,虽然计算任务一直在持续,但是并没有阻塞 "计时器" 的交互和更新。这会给交互体验,带来很大的改善。

使用空闲时执行计算任务,实现交互体验优化的关键有两个点:

  • 将计算任务的优先级降低,加入异步等待队列
  • 将大的计算任务拆分为小的任务,分片执行,当任务被执行时,不会长时间用户交互行为
相关推荐
柳杉8 分钟前
从动漫水面到赛博飞船:这位开发者的Three.js作品太惊艳了
前端·javascript·数据可视化
Greg_Zhong32 分钟前
前端基础知识实践总结,每日更新一点...
前端·前端基础·每日学习归类
We་ct1 小时前
LeetCode 148. 排序链表:归并排序详解
前端·数据结构·算法·leetcode·链表·typescript·排序算法
TON_G-T1 小时前
day.js和 Moment.js
开发语言·javascript·ecmascript
IT_陈寒1 小时前
JavaScript开发者必看:5个让你的代码性能翻倍的隐藏技巧
前端·人工智能·后端
Irene19911 小时前
JavaScript 中 this 指向总结和箭头函数的作用域说明(附:call / apply / bind 对比总结)
javascript·this·箭头函数
2501_921930832 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-appearance(更推荐自带的Appearance)
javascript·react native·react.js
还是大剑师兰特2 小时前
Vue3 中 computed(计算属性)完整使用指南
前端·javascript·vue.js
井川不擦2 小时前
前端安全通信方案:RSA + AES 混合加密
前端
孜孜不倦不忘初心2 小时前
Ant Design Vue 表格组件空数据统一处理 踩坑
前端·vue.js·ant design