狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

十萬條數據的優雅加載:分批渲染與虛擬列表

admin
2024年9月26日 13:34 本文熱度 840

一:問題分析

最近秋招開始面試了,在前端崗的面試中遇到這樣的一個情景題,這題目考察的是對前端性能優化的理解以及處理大數據量時的技術方案。下面帶友友們來剖一剖,首先我們先來一個小demo來看看一次性渲染十萬條數據的效果是怎么樣的,我們在一個HTML頁面中創建一個包含10萬個<li>元素的<ul>列表,并記錄整個過程的時間開銷

<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.innerHTML = ~~(Math.random() * total)

      ul.appendChild(li)

    }

    console.log('js運行耗時:', Date.now() - now);

    setTimeout(() => {

      console.log('頁面加載總時長:', Date.now() - now);    

    })     

  </script>

</body>

?

可以看到,導致頁面加載緩慢的是頁面渲染速度過慢,js的運行速度還算可以的。以上demo中是暴力渲染,接下來帶友友們了解兩個方法來加快渲染速度,提升用戶體驗感以及頁面渲染效率

二:分批渲染

遞歸渲染函數

  • loop函數接收兩個參數:當前還需要渲染的總數(curTotal)和當前已渲染的數量(curIndex),計算本次需要渲染的數量(pageCount),并且不超過剩余的數量。
  • 使用setTimeout來異步執行DOM操作,這允許瀏覽器有時間去處理其他任務(如事件處理、繪制等)。
  • setTimeout的回調函數中,使用for循環創建<li>元素,并將其追加到<ul>容器中。
  • 如果還有剩余數據需要渲染,則繼續遞歸調用loop函數。

<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.innerHTML = curIndex + i + ':' + ~~(Math.random() * total);

          ul.appendChild(li);

        }

        loop(curTotal - pageCount, curIndex + pageCount);

      })

    }

    loop(total, index);

  </script>

</body>

瀏覽器的刷新頻率通常是每秒60幀,即大約每16.7毫秒刷新一次,雖然使用setTimeout可以減輕瀏覽器的負擔,但setTimeout默認延遲時間為0,這意味著它會在當前任務隊列結束后執行,也就是說定時器生效時間并不是固定的。v8引擎的事件循環機制中,下一個事件不一定要等到16.7ms,但如果v8引擎沒有跟上,在一個或者多個16.7ms后沒有進入到下一個事件中,由于是非阻塞的,就可能造成它的執行時間與頁面的刷新時間并不完全同步。這意味著瀏覽器在渲染時可能無法及時更新屏幕,特別是在大量DOM操作的情況下。這可能導致以下問題:

  • 閃屏:當瀏覽器試圖渲染大量的DOM元素時,如果DOM操作過于密集,瀏覽器可能無法及時完成渲染,導致用戶看到部分渲染的內容,造成屏幕閃爍。
  • 白屏:在極端情況下,如果DOM操作過于復雜或耗時,瀏覽器可能無法在短時間內完成渲染,導致屏幕呈現為空白狀態,直到渲染完成。

我們將setTimeout改為requestAnimationFrame,可以很好解決這個不同步的問題,requestAnimationFrame具有以下特性:

  1. 同步刷新頻率requestAnimationFrame  雖然是嵌入到事件循環機制中的,但它是在渲染階段之前執行,而不是像  setTimeout  或  setInterval  那樣在回調隊列中排隊執行,并且requestAnimationFrame會在瀏覽器準備繪制下一幀前調用提供的回調函數,這樣可以確保動畫與屏幕刷新頻率同步。
  2. 性能優化:如果瀏覽器處于后臺或者標簽頁不可見狀態,requestAnimationFrame  會自動暫停,從而節省CPU資源。

解決了以上不同步的問題,還有性能方面的細節我們也要注意。由于不知道優化隊列具體能裝多少條數據,并且每循環一次就要回流重繪一次,因此以上的分批渲染會引起多次回流重繪。為了避免上述問題,可以使用文檔片段(Document Fragment)來構建DOM結構。

文檔片段是一個沒有標簽的節點,可以在內存中構建完整的DOM結構,然后再一次性插入到文檔中,這樣可以顯著減少頁面的回流次數。

requestAnimationFrame(() => {

  let fragment = document.createDocumentFragment();

  for (let i = 0; i < pageCount; i++) {

    let li = document.createElement("li");

    li.innerHTML = curIndex + i + ':' + ~~(Math.random() * total);

    fragment.appendChild(li);

  }

  ul.appendChild(fragment);

  loop(curTotal - pageCount, curIndex + pageCount);

})

三:虛擬列表

虛擬列表通過只渲染當前可視區域的數據,而不是整個數據集,從而減少DOM操作和提高了應用性能,虛擬列表的關鍵在于動態計算和渲染當前可視區域內的數據,并在用戶滾動時更新這些數據。

核心思路

  1. 獲取整個頁面的真實數據的高度。
  2. 計算可視區的高度以及其中可以放置的數據條數。
  3. 在用戶滾動頁面時,實時計算出起始下標和結束下標。
  4. 對樣式進行偏移,避免屏幕錯誤的移動。

下面用vue項目進行展示,帶友友們實現虛擬列表,主要涉及兩個頁面:App.vue和自定義組件virtualList.vue

3.1: 主組件App.vue

創建一個容器,用于展示虛擬列表組件, 將虛擬列表組件  virtualList  渲染到容器中,并傳遞  listData  屬性。

<template>

  <div class="app">

    <virtualList :listData="data" />

  </div>

</template>

導入  virtualList  組件。初始化  data  數組,包含 10 萬個對象,每個對象都有  id  和  value  屬性。

<script setup>

import virtualList from './components/virtualList.vue';

const data = []

for (let i = 0; i < 100000; i++) {

  data.push({id: i, value: i})

}

</script>

3.2: 自定義組件virtualList.vue

1: 模板部分

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

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

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

  • 容器

<div ref="listRef" class="infinite-list-container" @scroll="scrollEvent()">
    • 創建一個名為  .infinite-list-container  的  <div>  容器。
    • 通過  ref  獲取該元素的引用。
    • 添加  @scroll  事件監聽器,當容器滾動時觸發  scrollEvent  函數。
  • 占位符

<div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    • 創建一個名為  .infinite-list-phantom  的  <div>  占位符。
    • 設置占位符的高度為  listHeight
  • 實際列表

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

    • 創建一個名為  .infinite-list  的  <div>  實際列表。
    • 使用  :style  綁定屬性  transform  為  getTransform  的值。
    • 使用  v-for  循環遍歷  visibleData  數組,并渲染每個  item
    • 設置每個列表項的高度和行高。

2: 腳本部分

  1. 初始化容器和數據

    • 使用  ref  獲取列表容器的引用。
    • 初始化狀態對象  state,包括可視區高度、偏移量、起始索引和結束索引。
  2. 計算可視區數據

    • 計算可視區高度和可顯示的數據條數。
    • 通過  slice  方法截取當前可視區域內的數據片段。
  3. 動態更新列表

    • 在滾動事件中實時更新起始索引和結束索引,從而更新當前可視區域的數據。
    • 使用  transform  屬性對列表進行偏移,確保列表隨用戶的滾動而平滑移動。
  4. 樣式優化

    • 使用絕對定位和變換來控制列表的位置,減少DOM重排和重繪。
    • 通過占位符(phantom)來模擬整個列表的高度,確保滾動流暢。?

import { computed, nextTick, onMounted, reactive, ref } from 'vue';

const props = defineProps({

  listData: [],

  itemSize: {

    type: Number,

    default: 50

  }

})

const state = reactive({

  screenHeight: 0, 

  startOffset: 0,

  start: 0,

  end: 0

})

// 可視區顯示的數據條數

const visibleCount = computed(() => {

  return state.screenHeight / props.itemSize

})

// 可視區域顯示的真實數據

const visibleData = computed(() => {

  return props.listData.slice(state.start, Math.min(state.end, props.listData.length))

})

// 當前列表總高度

const listHeight = computed(() => {

  return props.listData.length * props.itemSize

})

// list跟著父容器移動了,現在列表要移動回來

const getTransform = computed(() => {

  return `translateY(${state.startOffset}px)`

})

const listRef = ref(null)

onMounted(() => {

  state.screenHeight = listRef.value.clientHeight

  state.end = state.start + visibleCount.value

})

const scrollEvent = () => {

  let scrollTop = listRef.value.scrollTop

  state.start = Math.floor(scrollTop / props.itemSize)

  state.end = state.start + visibleCount.value

  state.startOffset = scrollTop - (scrollTop % props.itemSize)

}

  • 導入和定義屬性

import { computed, nextTick, onMounted, reactive, ref } from 'vue';

const props = defineProps({

  listData: [],

  itemSize: {

    type: Number,

    default: 50

  }

})

const state = reactive({

  screenHeight: 0, 

  startOffset: 0,

  start: 0,

  end: 0

})

    • 導入必要的Vue Composition API函數。
    • 定義  props  屬性,包含  listData  和  itemSize
    • 使用  reactive  創建響應式狀態對象  state
  • 計算屬性

const visibleCount = computed(() => {

  return state.screenHeight / props.itemSize

3})

const visibleData = computed(() => {

  return props.listData.slice(state.start, Math.min(state.end, props.listData.length))

})

const listHeight = computed(() => {

  return props.listData.length * props.itemSize

})

const getTransform = computed(() => {

  return `translateY(${state.startOffset}px)`

})

    • visibleCount  計算可視區可以顯示的數據條數。
    • visibleData  計算當前可視區域的實際數據。
    • listHeight  計算整個列表的高度。
    • getTransform  計算列表的偏移量。
  • 引用和生命周期鉤子

const listRef = ref(null)

onMounted(() => {

  state.screenHeight = listRef.value.clientHeight

  state.end = state.start + visibleCount.value

})

    • 使用  ref  獲取容器的引用。
    • 在  onMounted  生命周期鉤子中初始化  screenHeight  和  end
  • 滾動事件處理

const scrollEvent = () => {

  let scrollTop = listRef.value.scrollTop

  state.start = Math.floor(scrollTop / props.itemSize)

  state.end = state.start + visibleCount.value

  state.startOffset = scrollTop - (scrollTop % props.itemSize)

}

    • scrollEvent  函數在滾動時更新  startend  和  startOffset

四:總結

在前端面試中探討一次性渲染十萬條數據的問題時,面試官主要想考察的是,是否理解性能優化的重要性,比如通過分頁或無限滾動來減少單次加載的數據量,是否掌握虛擬滾動技術,僅渲染當前可視區域的內容,以及是否了解如何利用虛擬DOMWeb Workers等技術來提升應用性能,確保良好的用戶體驗。

本文帶友友們實現了前兩種,至于Web Workers之后會單開一篇仔細講講。此外,在面試中遇到這樣的問題,友友們要有  性能意識,最好可以掌握  分頁技術懶加載(lazy loading)  ,  無限滾動(infinite scrolling)  ,  虛擬滾動(virtual scrolling)  。當然,像數據壓縮,服務器端渲染在某些場景下的優勢(如SEO),或者利用流式數據處理技術來逐步加載和渲染數據,也可以對性能進行優化。


本文來源于稀土掘金技術社區,作者:midsummer18

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


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