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

背景

在一次性需要处理大量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虚拟节点在开发中的应用

相关推荐
禅思院4 分钟前
路由性能高可用架构实战方案
前端·架构·前端框架
IT_陈寒21 分钟前
React状态更新总是不及时?你可能漏了这步批处理机制
前端·人工智能·后端
恋猫de小郭30 分钟前
AI Agent 开发究竟是啥?如何用 AI 开发 Agent ?深入浅出给你一套概念
android·前端·ai编程
前端双越老师33 分钟前
我开发 AI Agent 项目踩过的 5个坑
前端·agent·全栈
晓得迷路了1 小时前
栗子前端技术周刊第 134 期 - React Router v8、TypeScript 7 RC、React Native 0.86...
前端·javascript·react.js
Carson带你学Android1 小时前
Android 17 正式发布:AI 终于成了系统能力
android·前端·ai编程
Mike_jia1 小时前
ZbxTable:Zabbix开源报表神器,从运维数据到决策洞察的最后一公里
前端
LinXunFeng10 小时前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
乘风gg14 小时前
为什么AI 时代来临,大部分人吃不到红利
前端·ai编程·claude
恋猫de小郭14 小时前
Android 限制侧载新进展,谷歌联合国内厂商推验证计划
android·前端·flutter