当面试官问你一次渲染十万条数据 - 分时任务

71 阅读2分钟

当我们遇到任务密集型操作时我们需要考虑浏览器的性能压力,将某一段密集型操作分成几段任务,并且在浏览器空闲时进行操作,这将大大减少性能消耗

当我们面试时被问到如果后端一次性给你返回10万条数据,前端该怎么去处理,并将数据正常显示,当然对于我们前端来说我们可能直接内心翻腾,但是这种时候就是需要结合场景来解决问题,给钱的是大爷....

我们可以根据这个问题进行拆分:

  • 在浏览器中怎么去进行分段
  • 怎么知道浏览器空闲了,并且在浏览器下一次空闲时再执行任务
  • 怎么保证浏览器渲染正常

带着问题我们可以去调研,发现有个api叫做requestIdleCallback,他能在浏览器每一帧渲染的空闲时执行回调,所以之歌api完美适用我们的分段需求:

  const tasks = Array.from({ length: 500000 }, (_, i) => () => {
        const div = document.createElement("div");
        div.innerText = i;
        document.body.appendChild(div);
      });

上面的代码是创建一个50万的数组,每一个元素都是一个函数,执行的操作是创建一个div插入到body中,这毫无疑问是一个密集型操作,所以我们对该操作进行分段

  function idlePerformTask(tasks) {
        //先思考怎么去分时,通过
        performTask(tasks, (_runTask) => {
          //每一帧渲染后有空闲执行
          requestIdleCallback((IdleDeadline) => {
            // IdleDeadline.timeRemaining() > 0表示还有空闲
            _runTask(() => IdleDeadline.timeRemaining() > 0);
          });
        });
      }

然后我们再去进行具体操作的函数抽离

 function performTask(tasks, scheduler) {
   //记录下标拿到对应需要执行的函数
        let index = 0;
        const _run = () => {
          //调度器开始执行,此时的isGo就是() => IdleDeadline.timeRemaining() > 0这个函数
          scheduler((isGo) => {
            console.log(11111);
      //判断下标是否取完,然后浏览器是否空闲
            while (index < tasks.length && isGo()) {
              //执行插入元素操作
              tasks[index]();
              //下标自增
              index++;
            }
            //递归执行调用
            _run();
          });
        };
   
      //第一次执行一下
        _run();
      }

优化考虑防止_run空转递归加一下判断条件

 scheduler((isGo) => {
            console.log(11111);
​
            while (index < tasks.length && isGo()) {
              tasks[index]();
              index++;
            }
            if (index < tasks.length) {
              _run(); // 还有任务才继续调度
            }
          });