提示💡

  • 提前将数据分组
  • 使用slice获取

当面临需要插入大量数据到页面的情况时,下面是一些建议的处理方法:

  1. 分页加载:考虑将数据分成多个页面,每次只加载当前页面所需的数据。这样可以减少一次性加载大量数据对页面性能的影响,并提供更好的用户体验。

  2. 虚拟滚动:使用虚拟滚动技术,只渲染当前可见区域的数据,而不是全部渲染。这可以显著减少渲染时的性能开销,并提高滚动时的响应速度。

  3. 延迟加载:只在用户需要时加载数据。例如,当用户滚动到页面底部时,再加载下一页的数据。这样可以逐步加载数据,减轻页面的负担。

  4. 后台分页查询:考虑在后端实现分页查询,只返回当前页所需的数据量。通过限制每次请求的数据量,可以减少数据传输和页面渲染的时间。

  5. 数据缓存:如果数据是经常被使用的,并且不经常变化,可以考虑在前端进行数据缓存。这样可以避免每次加载都从后端请求数据,提高页面的加载速度。

以上这些方法可以根据具体的场景和需求进行组合使用。重要的是权衡性能和用户体验,确保页面加载和操作的流畅性。

下面是使用的 时间分片来处理

通过 setTimeout

直接上一个例子:

<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>十万数据渲染</title>
</head>
 
<body>
  <ul id="list-container"></ul>
 
  <script>
    const oListContainer = document.getElementById('list-container')
 
    const fetchData = () => {
      return new Promise(resolve => {
        const response = {
          code: 0,
          msg: 'success',
          data: [],
        }
 
        for (let i = 0; i < 100000; i++) {
          response.data.push(`content-${i + 1}`)
        }
 
        setTimeout(() => {
          resolve(response)
        }, 100)
      })
    }
 
    // 模拟请求后端接口返回十万条数据
    // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    const renderData = (data, total, page, pageCount) => {
      // base case -- total 为 0 时没有数据要渲染 不再递归调用
      if (total <= 0) return
 
      // total 比 pageCount 少时只渲染 total 条数据
      pageCount = Math.min(pageCount, total)
 
      setTimeout(() => {
        const startIdx = page * pageCount
        const endIdx = startIdx + pageCount
        const dataList = data.slice(startIdx, endIdx)
 
        // 将 pageCount 条数据插入到容器中
        for (let i = 0; i < pageCount; i++) {
          const oItem = document.createElement('li')
          oItem.innerText = dataList[i]
          oListContainer.appendChild(oItem)
        }
 
        renderData(data, total - pageCount, page + 1, pageCount)
      }, 0)
    }
 
    fetchData().then(res => {
      renderData(res.data, res.data.length, 0, 200)
    })
 
  </script>
</body>
 
</html>

上面的例子中,我们使用了 setTimeout,在每一次宏任务中插入一页数据,然后设置多个这样地宏任务,直到把所有数据都插入为止。

在这里插入图片描述

但是很明显能看到的问题是,快速拖动滚动条时,数据列表中会有闪烁的情况

这是因为:

JavaScript 代码在主线程上按照顺序执行,包括处理用户交互、运行脚本等。而 setTimeout 的回调函数会被添加到事件队列中,等待主线程上的所有任务完成后执行。 如果主线程上有其他任务(如计算密集型任务、大量的 DOM 操作等),即使设置了 0ms 的延迟,setTimeout 的回调函数也需要等待这些任务完成后才能执行。这意味着设置延迟时间为 0ms 并不能保证回调函数立即执行。 换句话说,尽管使用 setTimeout 分割任务可以让浏览器在处理大量 DOM 插入时更具有响应性,但它并不能保证立即执行回调函数。

所以,我们改善一下,通过 requestAnimationFrame 来处理

通过 requestAnimationFrame

<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>直接插入十万条数据</title>
</head>
 
<body>
  <ul id="list-container"></ul>
 
  <script>
    const oListContainer = document.getElementById('list-container')
 
    const fetchData = () => {
      return new Promise(resolve => {
        const response = {
          code: 0,
          msg: 'success',
          data: [],
        }
 
        for (let i = 0; i < 100000; i++) {
          response.data.push(`content-${i + 1}`)
        }
 
        setTimeout(() => {
          resolve(response)
        }, 100)
      })
    }
 
    // 模拟请求后端接口返回十万条数据
    // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    const renderData = (data, total, page, pageCount) => {
      // base case -- total 为 0 时没有数据要渲染 不再递归调用
      if (total <= 0) return
 
      // total 比 pageCount 少时只渲染 total 条数据
      pageCount = Math.min(pageCount, total)
 
      requestAnimationFrame(() => {
        const startIdx = page * pageCount
        const endIdx = startIdx + pageCount
        const dataList = data.slice(startIdx, endIdx)
 
        // 将 pageCount 条数据插入到容器中
        for (let i = 0; i < pageCount; i++) {
          const oItem = document.createElement('li')
          oItem.innerText = dataList[i]
          oListContainer.appendChild(oItem)
        }
 
        renderData(data, total - pageCount, page + 1, pageCount)
      })
    }
 
    fetchData().then(res => {
      renderData(res.data, res.data.length, 0, 200)
    })
 
  </script>
</body>
 
</html>

在这里插入图片描述 很明显,闪烁的问题被解决了

这是因为:

requestAnimationFrame 会在浏览器每次进行页面渲染时执行回调函数,保证了每次任务的执行间隔是稳定的,避免了丢帧现象。所以在处理大量 DOM 插入操作时,推荐使用 requestAnimationFrame 来拆分任务,以获得更流畅的渲染效果

扩展阅读