时间分片思想:多数据的前端处理方法

在前端开发中,我们时常会遇到需要处理大量数据渲染的场景 ------ 比如一次性插入 10000 条甚至更多数据到页面中。采用 "暴力破解" 的方式直接同步执行,往往会导致页面卡顿、白屏,严重影响用户体验。

时间分片(Time Slicing) 则是解决这一问题的核心思想,它通过 "拆分任务 + 异步调度" 的方式,让 JS 执行与页面渲染互不阻塞,实现流畅的数据处理与展示。

一、问题:为什么大量数据直接渲染会卡顿?

要理解时间分片的价值,首先我们要从浏览器的Event Loop(事件循环) 机制入手。

众所周知,浏览器的 JS 线程与渲染线程是 "互斥" 的 ------ 也就是说,当 JS 线程在执行同步代码时,渲染线程会被阻塞,导致无法更新页面,只有当 JS 线程空闲时,渲染线程才会执行页面重绘。

1.1 暴力破解的困境:同步执行的问题

为了让大家更好的感受到普通方法和时间分片的区别,现在,我将先展示最直接的暴力破解法,其核心逻辑就是通过for循环同步创建 10000 个li元素并插入页面:

html 复制代码
<ul id="container"></ul>
<script>
  let now = Date.now();
  const total = 100000;
  let ul = document.getElementById('container');
  // 同步循环创建并插入元素
  for(let i = 0; i < total; i++){
    let li = document.createElement('li');
    li.innerText = Math.random()*total;
    ul.appendChild(li);
  }
  console.log('JS运行时间', Date.now() - now); // 看似JS执行快
  setTimeout(()=>{
    console.log('总运行时间', Date.now() - now); // 实际总耗时远更长
  },0);
</script>

这是运行结束后的运行时间:

从这里可以看出,暴力破解法问题如下:

  • JS 线程阻塞 :100000 次createElementappendChild是同步任务,会占据 JS 线程较长时间(即使 JS 本身执行耗时短,DOM 操作的开销也会累积);
  • 渲染被推迟:在 JS 同步任务执行期间,渲染线程完全被阻塞,页面无法更新,用户会看到 "白屏" 或 "卡顿",直到所有 JS 任务结束后,渲染线程才会一次性完成所有元素的绘制;
  • 用户体验差:若数据量更大(比如 100 万条),那么很可能导致浏览器出现 "无响应" 。

二、时间分片思想

时间分片的本质是 **"化整为零" ------ 将原本需要一次性完成的大量任务(如 100000 条数据渲染),拆分成多个小批次任务(如每次渲染 10 条),并通过 异步调度机制 **(如setTimeoutrequestAnimationFrame)让这些小任务在 JS 线程空闲时执行。

其核心逻辑符合 Event Loop 的调度规则:

  1. 执行一小批任务(如渲染 10 条数据),耗时极短(通常 < 16ms,远小于屏幕刷新周期);
  2. 释放 JS 线程,让渲染线程执行页面更新(此时用户能看到已渲染的部分数据,无白屏);
  3. 等待下一次异步调度时机,重复执行下一批任务,直到所有数据处理完成。

通过这种方式,JS 执行与页面渲染交替进行,既完成了大量数据处理,又保证了页面的流畅性。

三、时间分片的实现方式

3.1 基础实现:基于 setTimeout 的异步调度

setTimeout是最基础的异步调度 API,它能将任务推入 "宏任务队列",待当前同步任务执行完毕、微任务队列清空后,再等待指定时间(此处设为 0ms,即尽快执行)执行。

代码示例:

html 复制代码
<ul id="container"></ul>
<script>
  let ul = document.getElementById('container');
  const total = 100000; // 总数据量
  const once = 50; // 每次渲染50条(批次大小,可调整)
  let index = 0; // 当前渲染的起始索引

  // 递归执行分片任务
  const loop = (curTotal, curIndex) => {
    // 终止条件:剩余数据为0时停止
    if (curTotal <= 0) return false;

    // 计算当前批次需渲染的数量(避免最后一批不足10条)
    const pageCount = Math.min(curTotal, once);

    // 异步执行当前批次渲染
    setTimeout(() => {
      for (let i = 0; i < pageCount; i++) {
        const li = document.createElement('li');
        li.innerText = `${curIndex + i}: ${Math.random() * total}`;
        ul.appendChild(li);
      }
      // 递归执行下一批:剩余数据量=当前总量-本批数量,起始索引=当前索引+本批数量
      loop(curTotal - pageCount, curIndex + pageCount);
    }, 0);
  };

  // 启动分片渲染
  loop(total, index);
</script>

核心逻辑解析:

  • 批次大小(once) :设为 50 是权衡 "渲染效率" 与 "流畅度" 的结果 ------ 批次太小会增加异步调度次数,批次太大仍可能阻塞,这个数据可根据实际数据量调整。
  • 递归终止 :当curTotal(剩余数据量)≤0 时,停止递归,避免无限调用;
  • 异步调度setTimeout(fn, 0)确保当前批次的 DOM 操作在 "下一次宏任务" 中执行,此时 JS 线程会先释放,让渲染线程更新已完成的部分。

不足:可能存在 "轻微白屏"

setTimeout的调度时机由 JS 引擎决定,不一定与浏览器的 "屏幕刷新周期"(通常为 60Hz,即每 16.6ms 刷新一次)同步。

若宏任务执行时机与渲染时机错位,在我们拖动进度条时,可能导致短暂的 "白屏"(虽比同步执行好很多,但体验仍有优化空间)。

3.2 优化实现:基于 requestAnimationFrame 的渲染同步

为解决setTimeout的 "时机错位" 问题,可使用浏览器原生 API------requestAnimationFrame(rAF) 。它的核心优势是:确保回调函数在 "屏幕每一次刷新前" 执行,与渲染周期完全同步,不会丢帧,彻底解决白屏问题。

实现代码(文档示例):

html 复制代码
<ul id="container"></ul>
<script>
  let ul = document.getElementById('container');
  const total = 100000;
  const once = 50;
  let index = 0;

  const loop = (curTotal, curIndex) => {
    if (curTotal <= 0) return false;

    const pageCount = Math.min(curTotal, once);

    // 用rAF替代setTimeout,与屏幕刷新同步
    requestAnimationFrame(() => {
      for (let i = 0; i < pageCount; i++) {
        const li = document.createElement('li');
        li.innerText = `${curIndex + i}: ${(Math.random() * total).toFixed(2)}`;
        ul.appendChild(li);
      }
      loop(curTotal - pageCount, curIndex + pageCount);
    });
  };

  loop(total, index);
</script>

核心优势:

  • 渲染同步:rAF 的回调会在浏览器准备好重绘时触发(约每 16.6ms 一次),确保当前批次的 DOM 操作完成后,渲染线程能立即更新页面,用户看到的是 "逐步流畅加载",无任何白屏;
  • 性能友好 :若浏览器标签页处于 "后台",rAF 会自动暂停,避免浪费 CPU 资源(setTimeout仍会执行)。

适用场景:

对渲染流畅度要求高的场景,如长列表渲染、大数据表格展示等。

四、两种实现方式的对比

特性 setTimeout 实现 requestAnimationFrame 实现
调度时机 宏任务队列,时机不固定 与屏幕刷新同步(≈16.6ms / 次)
白屏问题 可能存在轻微白屏 无白屏,渲染流畅
后台运行 仍会执行,消耗 CPU 后台暂停,节省资源
兼容性 所有浏览器支持 IE9 + 支持(现代浏览器均兼容)
适用场景 对流畅度要求不高的简单场景 长列表、大数据渲染等核心场景
相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax