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

背景

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

相关推荐
Cocktail_py6 小时前
JS如何调用wasm
开发语言·javascript·wasm
我有一棵树6 小时前
深入理解html 加载、解析、渲染和 DOMContentLoaded、onload事件
前端·性能优化·html
JIngJaneIL6 小时前
就业|高校就业|基于ssm+vue的高校就业信息系统的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·高校就业
G***T6917 小时前
前端构建工具环境变量,安全管理
前端
Want5957 小时前
HTML礼物圣诞树
前端·html
REDcker7 小时前
Cursor Chrome DevTools MCP 配置指南 for Windows
前端·windows·chrome devtools
张可爱7 小时前
20251115复盘记录:让分页乖乖“坐好”+ 卡片统一渐变描边与圆角
前端
Jonathan Star8 小时前
基于 **Three.js** 开发的 3D 炮弹发射特效系统
javascript·数码相机·3d
Cache技术分享8 小时前
241. Java 集合 - 使用 Collections 工厂类处理集合
前端·后端
Lear8 小时前
解决Flex布局中overflow:hidden失效
前端