用时间分片解决前端性能瓶颈的实战经验分享

a'ゞ增梅 优化 阅读 1,403
赞 15 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

最近在优化一个数据量超大的表格组件,渲染几千条数据的时候页面直接卡死。折腾了半天,发现时间分片(Time Slicing)是个不错的解法。简单来说,就是把耗时任务拆分成小块,分批执行。

用时间分片解决前端性能瓶颈的实战经验分享

来看个核心代码:

function timeSlice(tasks, callback) {
  let index = 0;
  const chunkSize = 5; // 每次处理5个任务

  function processChunk() {
    const currentTasks = tasks.slice(index, index + chunkSize);
    currentTasks.forEach(task => callback(task));
    index += chunkSize;

    if (index < tasks.length) {
      setTimeout(processChunk, 0); // 让出主线程
    }
  }

  processChunk();
}

// 使用示例
const data = Array.from({ length: 1000 }, (_, i) => i);
timeSlice(data, item => {
  console.log(Processing item ${item});
});

上面这段代码亲测有效,处理大量数据时页面不会卡死。关键是用setTimeout让出主线程,给浏览器机会去响应其他任务。

这个场景最好用

我主要在两个场景下会用到时间分片:一个是大数据渲染,另一个是复杂计算。比如最近做的那个表格组件,原本直接map渲染5000条数据,页面直接假死。改成时间分片后,虽然渲染时间变长了,但用户体验好多了。

这里有个实际项目中的用法:

function renderTable(data) {
  let index = 0;
  const batchSize = 50; // 每批渲染50行

  function renderBatch() {
    const batch = data.slice(index, index + batchSize);
    batch.forEach(row => {
      const tr = document.createElement('tr');
      Object.values(row).forEach(cell => {
        const td = document.createElement('td');
        td.textContent = cell;
        tr.appendChild(td);
      });
      document.querySelector('#table-body').appendChild(tr);
    });
    index += batchSize;

    if (index < data.length) {
      requestIdleCallback(renderBatch); // 更优雅的方式
    }
  }

  renderBatch();
}

// 调用
fetch('https://jztheme.com/api/large-data')
  .then(res => res.json())
  .then(data => renderTable(data));

这里用了requestIdleCallback,比setTimeout更智能,它会在浏览器空闲时执行回调。不过兼容性要注意下,IE不支持。

踩坑提醒:这三点一定注意

说几个我踩过的坑,大家引以为戒:

  • 过度分片导致性能问题:一开始我把每批数量设得太小(比如1-2个),结果频繁的函数调用开销反而拖慢了整体性能。建议根据实际情况测试,找到最佳的批次大小。
  • 状态管理要小心:如果在时间分片过程中修改了共享状态,可能会出现竞态条件。我在一个项目里就遇到过这种情况,最后加了个锁机制才解决。
  • 动画卡顿问题:虽然时间分片能让页面保持响应,但如果正好碰到CSS动画,可能会影响流畅度。建议在动画期间暂停分片任务。

高级技巧:配合Web Worker使用

对于特别耗时的计算任务,建议搭配Web Worker使用。我之前写了一个图片处理的功能,涉及到大量像素计算,单纯的时间分片还是会让页面有点卡。后来改成这样:

// 主线程
const worker = new Worker('worker.js');
let taskId = 0;
const taskMap = {};

function scheduleTask(data, callback) {
  const id = taskId++;
  taskMap[id] = callback;
  worker.postMessage({ id, data });
}

worker.onmessage = function(e) {
  const { id, result } = e.data;
  taskMap[id](result);
  delete taskMap[id];
};

// worker.js
self.onmessage = function(e) {
  const { id, data } = e.data;
  const result = heavyComputation(data); // 耗时计算
  self.postMessage({ id, result });
};

这种方式能把计算压力完全移到后台线程,前端只负责调度和展示。不过要注意,Web Worker不能直接操作DOM,所以需要做好前后台的数据通信。

这个技术还能玩出更多花样

时间分片其实还有很多拓展用法,比如:

  • 结合虚拟列表做无限滚动
  • 在动画帧之间插入微任务
  • 处理大规模文件上传/下载

以上是我个人对时间分片的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论