前端大数据渲染性能优化 - 分时函数的封装

背景

在一次性需要处理大量dom渲染操作时,直接渲染页面会非常耗费性能,从而阻塞js渲染进程,造成页面卡顿,比如在页面上一次性插入50万个dom操作,代码如下:

js 复制代码
for (let i = 0; i < 500000; i++) {
  const div = document.createElement('div');
  div.innerText = i;
  document.body.appendChild(div);
}

优化

在某些情况下,我们可以使用createDocumentFragment虚拟节点来减少频繁的dom操作

js 复制代码
const fragment = document.createDocumentFragment();
for (let i = 0; i < 5000; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  fragment.appendChild(div);
}
document.body.appendChild(fragment);

这段代码大大减少了频繁操作dom,只在最后一次性将dom挂载到页面,但最终一次性挂载50万个节点的dom还是会非常耗费性能,页面依然卡顿,但不否认它确实有一定的优化效果,只是在这里优化效果并不明显,所以我们需要将任务进行拆分,进行分批渲染,从而达到优化的目的

如上所述,首先创建一个任务列表tasks,然后在合适的时间去分批次执行它,这里直接使用Array.from直接创建性能会更好

这里我们选择使用requestIdleCallback,它的作用是在每一帧渲染后如果还有空余时间就会触发,当然如果没有空余时间就不会触发

js 复制代码
// 任务列表
const tasks = Array.from({ length: 500000 }, (_, i) => () => {
  const div = document.createElement('div');
  div.innerText = i;
  document.body.appendChild(div);
});

function performTask(tasks) {
  let index = 0;
  function _run() {
    requestIdleCallback((deadline) => {
      // 在任务没执行完并且空余时间大于0的情况下执行任务
      while (index < tasks.length && deadline.timeRemaining() > 0) {
        tasks[index++]();
      }
      if (index < tasks.length) {
        _run();
      }
    });
  }
  _run();
}

performTask(tasks);

这样就会在渲染空余时间执行一部分任务,直到任务执行完毕,不会阻塞渲染进程

到这里,代码其实已经优化完成,接下来写的是如何对这个函数进行封装,让它变得更通用

封装

要封装通用型函数,我们首先要去掉一些约束项,也就是每次的执行时机和每次执行的量,这里把requestIdleCallback替换成了需要参数传递的sheduler调度器函数,它也接收一个函数作为参数(中间的while if部分)

js 复制代码
function performTask(tasks, sheduler) {
  let index = 0;
  function _run() {
    sheduler((isGoOn) => {
      while (index < tasks.length && isGoOn()) {
        tasks[index++]();
      }
      if (index < tasks.length) {
        _run();
      }
    });
  }
  _run();
}

这里的runChunk执行的实际上就是while if那段代码:

js 复制代码
(isGoOn) => {
  while (index < tasks.length && isGoOn()) {
    tasks[index++]();
  }
  if (index < tasks.length) {
    _run();
  }
}

runChunk又接收一个函数来判断每次执行的量isGoOn,这里看起来很乱,来回嵌套,但是却非常的灵活,比如可以自定义一个调度器,让它每隔一秒钟执行三个任务

js 复制代码
function performTask(tasks, sheduler) {
  let index = 0;
  function _run() {
    sheduler((isGoOn) => {
      while (index < tasks.length && isGoOn()) {
        tasks[index++]();
      }
      if (index < tasks.length) {
        _run();
      }
    });
  }
  _run();
}

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

// 每隔1秒执行三个任务
const sheduler = (runChunk) => {
  let count = 0;
  setTimeout(() => {
    // runChunk(() => true); // 如果isGoOn为true,则会一直执行到任务完毕
    runChunk(() => count++ < 3); // 每次执行3个任务
  }, 1000);
  // 上面方便阅读,这里可以简写
  // setTimeout(() => runChunk(() => count++ < 3), 1000);
}
performTask(tasks, sheduler);

这还没完,你还可以使用requestAnimationFrame作为调度器来使用它,requestAnimationFrame这个方法是会在下一次渲染之前触发,一般用于动画渲染,在这里也可以用它来优化dom渲染

js 复制代码
// 其他代码如上...

const sheduler = (runChunk) => {
  let count = 0;
  requestAnimationFrame(() => {
    runChunk(() => count++ < 3);
  });
}
performTask(tasks, sheduler);

当然,通常情况下,我们使用requestIdleCallback情况相对会比较多,所以为了方便使用,我们可以再针对这个封装一个便携性的函数

js 复制代码
// 其他代码如上...

function idlePerformTask(tasks) {
  performTask(tasks, (runChunk) => {
    requestIdleCallback((deadline) => {
      runChunk(() => deadline.timeRemaining() > 0);
    });
  });
}

优化后的完整代码如下:

js 复制代码
function performTask(tasks, sheduler) {
  let index = 0;
  function _run() {
    sheduler((isGoOn) => {
      while (index < tasks.length && isGoOn()) {
        tasks[index++]();
      }
      if (index < tasks.length) {
        _run();
      }
    });
  }
  _run();
}

function idlePerformTask(tasks) {
  performTask(tasks, (runChunk) => {
    requestIdleCallback((deadline) => {
      runChunk(() => deadline.timeRemaining() > 0);
    });
  });
}

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

idlePerformTask(tasks);

总结

本文主要总结了前端大量渲染dom导致的页面卡顿的优化实践和分时函数的封装方法,要知道requestIdleCallbackrequestAnimationFrame的执行时机,也顺带提到了createDocumentFragment虚拟节点在开发中的应用

相关推荐
阿蒙Amon4 小时前
TypeScript学习-第7章:泛型(Generic)
javascript·学习·typescript
睡美人的小仙女1274 小时前
Threejs加载环境贴图报错Bad File Format: bad initial token
开发语言·javascript·redis
fanruitian5 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo5 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk5 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
摘星编程5 小时前
React Native + OpenHarmony:Timeline垂直时间轴
javascript·react native·react.js
2501_944525546 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
jin1233227 小时前
React Native鸿蒙跨平台完成剧本杀组队详情页面,可以复用桌游、团建、赛事等各类组队详情页开发
javascript·react native·react.js·ecmascript·harmonyos
李白你好7 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
经年未远8 小时前
vue3中实现耳机和扬声器切换方案
javascript·学习·vue