• 前端时间分片渲染


    在经典的面试题中:”如果后端返回了十万条数据要你插入到页面中,你会怎么处理?

    除了像 useVirtualList 这样的虚拟列表来处理外,我们还可以通过 时间分片 来处理

    通过 setTimeout

    直接上一个例子:

     

    1. <!--
    2.  * @Author: Jolyne
    3.  * @Date2023-09-22 15:45:45
    4.  * @LastEditTime: 2023-09-22 15:47:24
    5.  * @LastEditors: Jolyne
    6.  * @Description: 
    7. -->
    8. <!DOCTYPE html>
    9. <html lang="en">
    10. <head>
    11.   <meta charset="UTF-8" />
    12.   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    13.   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    14.   <title>十万数据渲染</title>
    15. </head>
    16. <body>
    17.   <ul id="list-container"></ul>
    18.   <script>
    19.     const oListContainer = document.getElementById('list-container')
    20.     const fetchData = () => {
    21.       return new Promise(resolve => {
    22.         const response = {
    23.           code0,
    24.           msg: 'success',
    25.           data: [],
    26.         }
    27.         for (let i = 0; i < 100000; i++) {
    28.           response.data.push(`content-${i + 1}`)
    29.         }
    30.         setTimeout(() => {
    31.           resolve(response)
    32.         }, 100)
    33.       })
    34.     }
    35.     // 模拟请求后端接口返回十万条数据
    36.     // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    37.     const renderData = (data, total, page, pageCount) => {
    38.       // base case -- total 为 0 时没有数据要渲染 不再递归调用
    39.       if (total <= 0return
    40.       // total 比 pageCount 少时只渲染 total 条数据
    41.       pageCount = Math.min(pageCount, total)
    42.       setTimeout(() => {
    43.         const startIdx = page * pageCount
    44.         const endIdx = startIdx + pageCount
    45.         const dataList = data.slice(startIdx, endIdx)
    46.         // 将 pageCount 条数据插入到容器中
    47.         for (let i = 0; i < pageCount; i++) {
    48.           const oItem = document.createElement('li')
    49.           oItem.innerText = dataList[i]
    50.           oListContainer.appendChild(oItem)
    51.         }
    52.         renderData(data, total - pageCount, page + 1, pageCount)
    53.       }, 0)
    54.     }
    55.     fetchData().then(res => {
    56.       renderData(res.data, res.data.length0200)
    57.     })
    58.   </script>
    59. </body>
    60. </html>

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

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

    这是因为:

    当使用 setTimeout 来拆分大量的 DOM 插入操作时,虽然我们将延迟时间设置为 0ms,但实际上由于 JavaScript 是单线程的,任务执行时会被放入到事件队列中,而事件队列中的任务需要等待当前任务执行完成后才能执行。所以即使设置了 0ms 延迟,setTimeout 的回调函数也不一定会立即执行,可能会受到其他任务的阻塞。

    当 setTimeout 的回调函数执行的间隔超过了浏览器每帧更新的时间间隔(一般是 16.7ms),就会出现丢帧现象。丢帧指的是浏览器在更新页面时,没有足够的时间执行全部的任务,导致部分任务被跳过,从而导致页面渲染不连续,出现闪烁的情况

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

    通过 requestAnimationFrame

    1. <!--
    2.  * @Author: Jolyne
    3.  * @Date2023-09-22 15:45:45
    4.  * @LastEditTime: 2023-09-22 15:47:24
    5.  * @LastEditors: Jolyne
    6.  * @Description: 
    7. -->
    8. <!DOCTYPE html>
    9. <html lang="en">
    10. <head>
    11.   <meta charset="UTF-8" />
    12.   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    13.   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    14.   <title>直接插入十万条数据</title>
    15. </head>
    16. <body>
    17.   <ul id="list-container"></ul>
    18.   <script>
    19.     const oListContainer = document.getElementById('list-container')
    20.     const fetchData = () => {
    21.       return new Promise(resolve => {
    22.         const response = {
    23.           code0,
    24.           msg: 'success',
    25.           data: [],
    26.         }
    27.         for (let i = 0; i < 100000; i++) {
    28.           response.data.push(`content-${i + 1}`)
    29.         }
    30.         setTimeout(() => {
    31.           resolve(response)
    32.         }, 100)
    33.       })
    34.     }
    35.     // 模拟请求后端接口返回十万条数据
    36.     // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    37.     const renderData = (data, total, page, pageCount) => {
    38.       // base case -- total 为 0 时没有数据要渲染 不再递归调用
    39.       if (total <= 0return
    40.       // total 比 pageCount 少时只渲染 total 条数据
    41.       pageCount = Math.min(pageCount, total)
    42.       requestAnimationFrame(() => {
    43.         const startIdx = page * pageCount
    44.         const endIdx = startIdx + pageCount
    45.         const dataList = data.slice(startIdx, endIdx)
    46.         // 将 pageCount 条数据插入到容器中
    47.         for (let i = 0; i < pageCount; i++) {
    48.           const oItem = document.createElement('li')
    49.           oItem.innerText = dataList[i]
    50.           oListContainer.appendChild(oItem)
    51.         }
    52.         renderData(data, total - pageCount, page + 1, pageCount)
    53.       })
    54.     }
    55.     fetchData().then(res => {
    56.       renderData(res.data, res.data.length0200)
    57.     })
    58.   </script>
    59. </body>
    60. </html>

     

    很明显,闪烁的问题被解决了

    这是因为:

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

  • 相关阅读:
    c++继承
    [Python急救站]基于Transformer Models模型完成GPT2的学生AIGC学习训练模型
    基于Qt实现文本编辑器功能
    Uniapp学习(1)Uniapp是什么,HBuilder软件介绍
    android recyclerView緩存數量 recycleview的缓存
    集业界最优资源,SAIC AI LAB 2.0技术架构如何将Robotaxi量产变为可能?
    IntelliJ IDEA社区版学习一
    Web自动化测试-Selenium语法入门到精通
    网络编程Demo:Java的阻塞与非阻塞模式以及Netty
    远程服务器连接(密钥验证,跳板机)
  • 原文地址:https://blog.csdn.net/why_1639946449/article/details/134024370