javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript时间分片

JavaScript通过时间分片解决页面卡顿的方法介绍

作者:轻语呢喃

在前端开发中,我们时常会遇到需要处理大量数据渲染的场景,往往会导致页面卡顿,时间分片(Time Slicing)  则是解决这一问题的核心思想,下面就跟随小编一起来了解下吧

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

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

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

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

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

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

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

<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>

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

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

二、时间分片思想

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

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

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

三、时间分片的实现方式

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

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

代码示例:

<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>

核心逻辑解析:

不足:可能存在 “轻微白屏”

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

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

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

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

实现代码(文档示例):

<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>

核心优势:

适用场景:

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

四、两种实现方式的对比

特性setTimeout 实现requestAnimationFrame 实现
调度时机宏任务队列,时机不固定与屏幕刷新同步(≈16.6ms / 次)
白屏问题可能存在轻微白屏无白屏,渲染流畅
后台运行仍会执行,消耗 CPU后台暂停,节省资源
兼容性所有浏览器支持IE9 + 支持(现代浏览器均兼容)
适用场景对流畅度要求不高的简单场景长列表、大数据渲染等核心场景

到此这篇关于JavaScript通过时间分片解决页面卡顿的方法介绍的文章就介绍到这了,更多相关JavaScript时间分片内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文