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

我们知道,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>

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

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

  • 将计算任务的优先级降低,加入异步等待队列
  • 将大的计算任务拆分为小的任务,分片执行,当任务被执行时,不会长时间用户交互行为
相关推荐
Asort7 小时前
JavaScript 从零开始(三):浏览器控制台与VS Code协同工作环境搭建详解
前端·javascript
跟橙姐学代码7 小时前
自动化邮件发送的终极秘籍:Python库smtplib与email的完整玩法
前端·python·ipython
葡萄城技术团队7 小时前
浏览器为啥要对 JavaScript 定时器“踩刹车”?
javascript
我是ed8 小时前
# vue3 实现甘特图
前端
m0_616188498 小时前
el-table的隔行变色不影响row-class-name的背景色
前端·javascript·vue.js
zheshiyangyang8 小时前
Vue3组件数据双向绑定
前端·javascript·vue.js
xw58 小时前
uni-app项目支付宝端Input不受控
前端·uni-app·支付宝
大翻哥哥9 小时前
Python上下文管理器进阶指南:不仅仅是with语句
前端·javascript·python
IT_陈寒9 小时前
React 性能优化必杀技:这5个Hook组合让你的应用提速50%!
前端·人工智能·后端
再吃一根胡萝卜9 小时前
Git 强制推送指南:新手必读的「危险操作」解析
前端