公交车上荫蒂添的好舒服的电影-公用玩物(np双xing总受)-公用小荡货芊芊-公与妇仑乱hd-攻把受做哭边走边肉楼梯play-古装一级淫片a免费播放口

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【JavaScript】WEB開發(fā)時如何實現(xiàn)一次性渲染十萬條數(shù)據(jù)

admin
2024年8月28日 22:39 本文熱度 1392

本文轉(zhuǎn)載于稀土掘金技術(shù)社區(qū),作者:反應(yīng)熱

原文鏈接:https://juejin.cn/post/7407763018471948325

前言

當(dāng)面試官問:給你十萬條數(shù)據(jù),你會怎么辦?這時我們該如何應(yīng)對呢?

在實際的Web開發(fā)中,有時我們需要在頁面上展示大量的數(shù)據(jù),比如用戶評論、商品列表等。如果一次性渲染太多的數(shù)據(jù)(如100,000條數(shù)據(jù)),直接將所有數(shù)據(jù)一次性渲染到頁面上會導(dǎo)致瀏覽器卡頓,用戶體驗變差。下面我們從一個簡單的例子開始,逐步改進(jìn)代碼,直到使用現(xiàn)代框架的虛擬滾動技術(shù)來解決這個問題,看完本文后,你就可以跟面試官侃侃而談了。

正文

最直接的方法

下面是最直接的方法,一次性創(chuàng)建所有的列表項并添加到DOM樹中。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <ul id="container"></ul>

        <script>
            let ul=document.getElementById('container');
            const total=100000
            let now=Date.now()
            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)
            setTimeout(()=>{
                console.log('運行耗時',Date.now()-now)
            })
        </script>

    </body>
    </html>

image.png

代碼解釋:

  • 我們獲取了一個<ul>元素,并定義了一個總數(shù)total為1000,使用for循環(huán)來創(chuàng)建<li>元素,并給每個元素設(shè)置一個文本值,~~為向下取整, 每個新創(chuàng)建的<li>都被添加到<ul>元素中。
  • 我們記錄了整個過程的耗時,可以看到js引擎在編譯完代碼只花了92ms還是非常快的。
  • 而定時器耗時了3038ms,我們知道js引擎是單線程工作的,首先它會執(zhí)行同步代碼,然后再執(zhí)行微任務(wù),接著再在瀏覽器上渲染,最后執(zhí)行宏任務(wù),setTimeout這里我們?nèi)藶榈膶懸粋€宏任務(wù),這個打印的出來時間可以看成開始運行代碼再到瀏覽器把數(shù)據(jù)渲染所花的時間對吧,可以看到還是要一會的對吧。

結(jié)論: 這種方法雖然實現(xiàn)起來簡單直接,但由于它在一個循環(huán)中創(chuàng)建并添加了所有列表項至DOM樹,因此在執(zhí)行過程中,瀏覽器需要等待JavaScript完全執(zhí)行完畢才能開始渲染頁面。當(dāng)數(shù)據(jù)量非常大(例如本例中的100,000個列表項)時,這種大量的DOM操作會導(dǎo)致瀏覽器的渲染隊列積壓大量工作,從而引發(fā)頁面的回流與重繪,瀏覽器無法進(jìn)行任何渲染操作,導(dǎo)致了所謂的“阻塞”渲染。

setTimeout分批渲染

為了避免一次性操作引起瀏覽器卡頓,我們可以使用setTimeout將創(chuàng)建和添加操作分散到多個時間點,每次只渲染一部分數(shù)據(jù)。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <ul id="container"></ul>

        <script>
            let ul=document.getElementById('container');
            const total=100000
            let once= 20
            let page=total/once
            let index=0

            function loop(curTotal,curIndex){
                let pageCount=Math.min(once,curTotal)
                setTimeout(()=>{
                    for(let i=0;i<pageCount;i++){
                        let li=document.createElement('li');
                        li.innerText=curIndex+i+':'+~~(Math.random()*total)
                        ul.appendChild(li)
                    }
                    loop(curTotal-pageCount,curIndex+pageCount)
                })
            }
            loop(total,index)
        </script>

    </body>
    </html>

代碼解釋:

  • 這里我們將所有數(shù)據(jù)分批渲染,每批次添加20個元素,因為到最后可能會不足20個所有我們用Math.min(once,curTotal)取兩者小的那個,如果還有剩余的元素需要添加,則遞歸調(diào)用loop函數(shù)繼續(xù)處理,每次遞歸減去相應(yīng)數(shù)量。
  • 首先上來執(zhí)行一遍,同步,異步,然后渲染,啥也沒有渲染對吧,然后執(zhí)行setTimeout也就是宏任務(wù),然后再向剛剛一樣同步,異步,然后渲染,這時候可以渲染20條數(shù)據(jù),接著再這樣一直遞歸到數(shù)據(jù)加載完畢。

結(jié)論:

  • 這里就是把瀏覽器渲染時的壓力分?jǐn)偨o了js引擎js引擎是單線程工作的,先執(zhí)行同步,異步,然后瀏覽器渲染,再宏任務(wù),這里就很好的利用了這一點,把渲染的任務(wù)分批執(zhí)行,減輕了瀏覽器一次要渲染大量數(shù)據(jù)造成的渲染“阻塞”,也很好的解決了數(shù)據(jù)過多時可能造成頁面卡頓或白屏的問題,
  • 但是有點小問題,我們現(xiàn)在用的電腦屏幕刷新率基本上都是60Hz,意味著它每秒鐘可以刷新顯示60次新的畫面。如果我們以此為例計算,那么兩次刷新之間的時間間隔大約是16.67毫秒,如果說當(dāng)執(zhí)行本次宏任務(wù)里的同步,異步,然后渲染這個時間點是在16.67ms以后也就是屏幕畫面剛刷新完以后,是不是得等到下一次的16.67ms屏幕畫面刷新才能有數(shù)據(jù)看到,所有當(dāng)用戶往下翻的時候有可能那一瞬間看不到東西,但是很快馬上就有了,這個問題不是你迅速往下拉數(shù)據(jù)沒加載那個,這個問題現(xiàn)在是不法完成避免的。

使用requestAnimationFrame

requestAnimationFrame是一個比setTimeout更優(yōu)秀的解決方案,因為它就是屏幕刷新率的時間。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <ul id="container"></ul>

        <script>
            let ul=document.getElementById('container');
            const total=100000
            let once= 20
            let page=total/once
            let index=0

            function loop(curTotal,curIndex){
                let pageCount=Math.min(once,curTotal)
                requestAnimationFrame(()=>{
                    for(let i=0;i<pageCount;i++){
                        let li=document.createElement('li');
                        li.innerText=curIndex+i+':'+~~(Math.random()*total)
                        ul.appendChild(li)
                    }
                    loop(curTotal-pageCount,curIndex+pageCount)
                })
            }
            loop(total,index)
        </script>

    </body>
    </html>

代碼解釋:

  • 和使用setTimeout類似,這里我們也使用分批處理。
  • 不同之處在于使用了requestAnimationFrame代替setTimeout,這使得操作更加流暢,就是在屏幕畫面刷新的時候渲染,就避免了上面的問題。

結(jié)論: 通過requestAnimationFrame代替setTimeout,在屏幕畫面刷新的時候渲染,就避免了上面setTimeout可能出現(xiàn)的問題。

使用文檔碎片(requsetAnimationFrame+DocuemntFragment )

文檔碎片是一種可以暫時存放DOM節(jié)點的“容器”,它不會出現(xiàn)在文檔流中。當(dāng)所有節(jié)點都準(zhǔn)備好之后,再一次性添加到DOM中,可以減少DOM操作次數(shù)。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <ul id="container"></ul>

        <script>
            let ul=document.getElementById('container');
            const total=100000
            let once= 20
            let page=total/once
            let index=0

            function loop(curTotal,curIndex){
                let fragment =document.createDocumentFragment(); //創(chuàng)建文檔碎片
                let pageCount=Math.min(once,curTotal)
                requestAnimationFrame(()=>{
                    for(let i=0;i<pageCount;i++){
                        let li=document.createElement('li');
                        li.innerText=curIndex+i+':'+~~(Math.random()*total)
                        fragment.appendChild(li)
                    }
                    ul.appendChild(fragment)
                    loop(curTotal-pageCount,curIndex+pageCount)
                })
            }
            loop(total,index)
        </script>

    </body>
    </html>

代碼解釋:

  • 創(chuàng)建一個DocumentFragment實例fragment來暫存<li>元素,在循環(huán)內(nèi)部,將生成的<li>元素添加到fragment中,你可以理解為一個虛假的標(biāo)簽,把<li>掛在這個標(biāo)簽上,只不過這個標(biāo)簽不會出現(xiàn)在DOM中。
  • 循環(huán)結(jié)束后,一次性將fragment添加到<ul>元素中,這樣就減少了DOM操作次數(shù),提高了性能。

結(jié)論: 通過使用 DocumentFragment,可以在內(nèi)存中暫存一組 DOM 節(jié)點,直到這些節(jié)點被一次性添加到 DOM 樹中。這樣做可以減少 DOM 的重排和重繪次數(shù),從而提高性能這對于提高頁面性能是非常重要的,尤其是在進(jìn)行大量的DOM更新時。

用虛擬滾動(Virtual Scrolling)

對于非常大的數(shù)據(jù)集,最佳實踐是使用虛擬滾動技術(shù),現(xiàn)在很多公司都是用的這種方法。虛擬滾動只渲染當(dāng)前可視區(qū)域內(nèi)的數(shù)據(jù),當(dāng)用戶滾動時,動態(tài)替換這些數(shù)據(jù)。

這里使用vue實現(xiàn)一個簡單的虛擬滾動列表。

image.png

就兩個文件

App.vue

    <template>
      <div class="app">
        <virtualList :listData="data"></virtualList>
      </div>
    </template>

    <script setup>
    import virtualList from './components/virtualList.vue'

    // 創(chuàng)建一個包含10萬條數(shù)據(jù)的大數(shù)組
    const data = []
    for (let i = 0; i < 100000; i++) {
      data.push({ id: i, value: i })
    }
    </script>


    <style lang="css" scoped>
    .app {
      height400px/* 設(shè)置可視區(qū)域的高度 */
      width300px/* 設(shè)置可視區(qū)域的寬度 */
      border1px solid #000/* 邊框,便于看到邊界 */
    }
    </style>

virtualList.vue

    <template>
      <!-- 可視區(qū)域 -->
      <div ref="listRef" class="infinite-list-container" @scroll="scrollEvent()">
        <!-- 虛擬高度占位符 -->
        <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>

        <!-- 動態(tài)渲染數(shù)據(jù)的區(qū)域 -->
        <div class="infinite-list" :style="{ transform: getTransform }">
          <div 
            class="infinite-list-item" 
            v-for="item in visibleData" 
            :key="item.id"
            :style="{ height: itemSize + 'px', lineHeight: itemSize + 'px' }"
          >

            {{ item.value }}
          </div>
        </div>
      </div>
    </template>

    <script setup>
    import { computed, nextTick, onMounted, ref } from 'vue';

    // 定義接收的屬性
    const props = defineProps({
      listDataArray,
      itemSize: {
        typeNumber,
        default50
      }
    });

    // 反應(yīng)式狀態(tài)
    const state = reactive({
      screenHeight0// 可視區(qū)域高度
      startOffset0// 當(dāng)前偏移量
      start0// 開始索引
      end0 // 結(jié)束索引
    });

    // 計算屬性
    const visibleCount = computed(() => {
      return Math.ceil(state.screenHeight / props.itemSize); // 可視區(qū)域內(nèi)能顯示的項目數(shù)量
    });

    const visibleData = computed(() => {
      return props.listData.slice(state.start, Math.min(state.end, props.listData.length)); // 當(dāng)前可視數(shù)據(jù)
    });

    const listHeight = computed(() => {
      return props.listData.length * props.itemSize; // 列表總高度
    });

    const getTransform = computed(() => {
      return `translateY(${state.startOffset}px)`// 計算transform值
    });

    // 引用元素
    const listRef = ref(null);

    // 生命周期鉤子
    onMounted(() => {
      state.screenHeight = listRef.value.clientHeight; // 初始化可視區(qū)域高度
      state.end = state.start + visibleCount.value; // 初始化結(jié)束索引
    });

    // 滾動事件處理
    const scrollEvent = () => {
      const scrollTop = listRef.value.scrollTop; // 當(dāng)前滾動距離
      state.start = Math.floor(scrollTop / props.itemSize); // 計算開始索引
      state.end = state.start + visibleCount.value; // 更新結(jié)束索引
      state.startOffset = scrollTop - (scrollTop % props.itemSize); // 更新偏移量
    };
    </script>


    <style lang="css" scoped>
    .infinite-list-container {
      height100%/* 占滿整個父容器高度 */
      overflow: auto; /* 允許滾動 */
      position: relative; /* 使內(nèi)部元素可以相對于它定位 */
    }

    .infinite-list-phantom {
      position: absolute; /* 絕對定位 */
      left0;
      right0/* 寬度充滿整個容器 */
      top0/* 頂部對齊 */
      z-index: -1/* 放在底層 */
    }

    .infinite-list {
      position: absolute; /* 絕對定位 */
      left0;
      right0/* 寬度充滿整個容器 */
      top0/* 頂部對齊 */
      text-align: center; /* 文本居中 */
    }

    .infinite-list-item {
      border-bottom1px solid #eee/* 分隔線 */
      box-sizing: border-box; /* 包含邊框和內(nèi)邊距 */
    }
    </style>



**代碼解釋:**

`可視區(qū)域`

  
    <div ref="listRef" class="infinite-list-container" @scroll="scrollEvent()"></div>

![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/db74bf871da94fb3b45d8e91cdb1e782~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgc29ycnloYw==:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzA2MTQ3NjEzMDA0NDQ4NyJ9\&rk3s=e9ecf3d6\&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018\&x-orig-expires=1724928998\&x-orig-sign=p2QyI1b1YnbRxmYCIkATvAiwBuc%3D)

這個是可視的區(qū)域,就好比電腦和手機(jī)能看到東西的窗口大小,這是用戶實際可以看到的區(qū)域,它有一個固定的大小,并且允許滾動

`虛擬高度占位符`

```html
<div class="infinite-list-phantom" :style="{height: listHeight + 'px'}"></div>

 

這個占位符的作用是模擬整個數(shù)據(jù)集的高度,即使實際上并沒有渲染所有的數(shù)據(jù)項。它是一個不可見的元素,高度等于所有數(shù)據(jù)項的高度之和。

動態(tài)渲染數(shù)據(jù)的區(qū)域

 <div class="infinite-list" :style="{transform: getTransform}"></div>

image.png

這部分負(fù)責(zé)實際顯示數(shù)據(jù)項,和可視化的區(qū)域一樣大,它通過 transform 屬性調(diào)整位置,確保只顯示當(dāng)前可視區(qū)域內(nèi)的數(shù)據(jù)項。

核心實現(xiàn)原理: 先拿到所有數(shù)據(jù)的占的區(qū)域,當(dāng)往下滾動的時候,整個所有區(qū)域的數(shù)據(jù)會往上走(也就是這個div class="infinite-list-phantom"),而我們現(xiàn)在這個區(qū)域(div class="infinite-list")就是跟用戶看到的數(shù)據(jù)區(qū)域一樣大的區(qū)域也會往上滾,可以保證給的數(shù)據(jù)是正確的數(shù)據(jù),當(dāng)往上滾時,用戶看到數(shù)據(jù)會更新并且會往上移動,變得越來越少,我們通過 transform 屬性調(diào)整位置把它移動到我們固定的可視化的區(qū)域(div ref="listRef" class="infinite-list-container"),給用戶看的數(shù)據(jù)就是完整的數(shù)據(jù)了。也就相當(dāng)于我們這個有全部的虛假數(shù)據(jù)大小,我們只截取用戶能看到的真實的部分?jǐn)?shù)據(jù)給他們看。

結(jié)論: 虛擬滾動的核心思想是只渲染當(dāng)前可視區(qū)域的數(shù)據(jù),而不是一次性渲染整個數(shù)據(jù)集。這在處理大數(shù)據(jù)量時尤為重要,因為它可以顯著提高應(yīng)用的性能和響應(yīng)速度。

總結(jié)


通過上述五個方法,我們從最基本的DOM操作的方法到使用現(xiàn)代前端技術(shù)使用的方法,本文到此就結(jié)束了,希望對你有所幫助!


該文章在 2024/8/29 12:22:56 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产一级无码不卡视频 | 国产精品免费播放久久 | 2025国产麻豆剧果冻传媒免费 | 国产高潮视频在线观看 | 精品日韩一区 | 成人品视频观看在线 | a极毛片一区二区三区免费看 | 高潮湖久久久久久久久 | 国产精品午夜福利在线观看 | 精品国产一区二区三区不卡在线 | 国产一级片内射在线视频播放 | ts清晰版在线观看 | 精品亚洲a∨乱码一区二区三区 | 99久久久无码国产精品9 | 99久久精品无码一区二区涩爱 | 国产v一区二区久久久 | 囯产精品一区二区免费在线观看 | 国产精品视频一区二区三区不卡 | 国产精品麻豆专区 | 精品国产精品va在线观看 | 国产不卡的一区二区三区四区 | 成人精品一区二区三区免费视频 | 2025国产成人精品久久 | 国产美女裸体无遮掩免费牛牛 | 国产精品入口麻豆免 | 国产成人精品高清在线观看96 | 国产一区二区三区不卡在线观看 | 国产夫妻对 | 国产精品一区二区三区久久久久 | 国产极品白嫩精品无码视频 | av在线网站观看网址入口 | 国产孕妇故爱a级高清片免费看 | 91人妻人人做人碰人人爽九色 | 国产aⅴ激情无码久久久无码 | 国产日韩亚洲欧洲一区二区三区 | 激情伊人五月天久久综合 | 91麻豆成人精品国产免费软件 | 成人国产一区二区三区精品不 | 国产欧美日韩一区二区三区精品 | 国产美女人喷水在线观看 | 3d动漫精品啪啪一区二区下载 |