欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

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

十萬條數(shù)據(jù)的優(yōu)雅加載:分批渲染與虛擬列表

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

一:問題分析

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

<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運(yùn)行耗時(shí):', Date.now() - now);

    setTimeout(() => {

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

    })     

  </script>

</body>

?

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

二:分批渲染

遞歸渲染函數(shù)

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

<body>

  <ul id="container"></ul>

  <script>

    let ul = document.getElementById("container");

    const total = 100000;

    let once = 20 //單次渲染數(shù)

    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可以減輕瀏覽器的負(fù)擔(dān),但setTimeout默認(rèn)延遲時(shí)間為0,這意味著它會(huì)在當(dāng)前任務(wù)隊(duì)列結(jié)束后執(zhí)行,也就是說定時(shí)器生效時(shí)間并不是固定的。v8引擎的事件循環(huán)機(jī)制中,下一個(gè)事件不一定要等到16.7ms,但如果v8引擎沒有跟上,在一個(gè)或者多個(gè)16.7ms后沒有進(jìn)入到下一個(gè)事件中,由于是非阻塞的,就可能造成它的執(zhí)行時(shí)間與頁面的刷新時(shí)間并不完全同步。這意味著瀏覽器在渲染時(shí)可能無法及時(shí)更新屏幕,特別是在大量DOM操作的情況下。這可能導(dǎo)致以下問題:

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

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

  1. 同步刷新頻率requestAnimationFrame  雖然是嵌入到事件循環(huán)機(jī)制中的,但它是在渲染階段之前執(zhí)行,而不是像  setTimeout  或  setInterval  那樣在回調(diào)隊(duì)列中排隊(duì)執(zhí)行,并且requestAnimationFrame會(huì)在瀏覽器準(zhǔn)備繪制下一幀前調(diào)用提供的回調(diào)函數(shù),這樣可以確保動(dòng)畫與屏幕刷新頻率同步。
  2. 性能優(yōu)化:如果瀏覽器處于后臺(tái)或者標(biāo)簽頁不可見狀態(tài),requestAnimationFrame  會(huì)自動(dòng)暫停,從而節(jié)省CPU資源。

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

文檔片段是一個(gè)沒有標(biāo)簽的節(jié)點(diǎn),可以在內(nèi)存中構(gòu)建完整的DOM結(jié)構(gòu),然后再一次性插入到文檔中,這樣可以顯著減少頁面的回流次數(shù)。

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

})

三:虛擬列表

虛擬列表通過只渲染當(dāng)前可視區(qū)域的數(shù)據(jù),而不是整個(gè)數(shù)據(jù)集,從而減少DOM操作和提高了應(yīng)用性能,虛擬列表的關(guān)鍵在于動(dòng)態(tài)計(jì)算和渲染當(dāng)前可視區(qū)域內(nèi)的數(shù)據(jù),并在用戶滾動(dòng)時(shí)更新這些數(shù)據(jù)。

核心思路

  1. 獲取整個(gè)頁面的真實(shí)數(shù)據(jù)的高度。
  2. 計(jì)算可視區(qū)的高度以及其中可以放置的數(shù)據(jù)條數(shù)。
  3. 在用戶滾動(dòng)頁面時(shí),實(shí)時(shí)計(jì)算出起始下標(biāo)和結(jié)束下標(biāo)。
  4. 對樣式進(jìn)行偏移,避免屏幕錯(cuò)誤的移動(dòng)。

下面用vue項(xiàng)目進(jìn)行展示,帶友友們實(shí)現(xiàn)虛擬列表,主要涉及兩個(gè)頁面:App.vue和自定義組件virtualList.vue

3.1: 主組件App.vue

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

<template>

  <div class="app">

    <virtualList :listData="data" />

  </div>

</template>

導(dǎo)入  virtualList  組件。初始化  data  數(shù)組,包含 10 萬個(gè)對象,每個(gè)對象都有  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()">
    • 創(chuàng)建一個(gè)名為  .infinite-list-container  的  <div>  容器。
    • 通過  ref  獲取該元素的引用。
    • 添加  @scroll  事件監(jiān)聽器,當(dāng)容器滾動(dòng)時(shí)觸發(fā)  scrollEvent  函數(shù)。
  • 占位符

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

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

    • 創(chuàng)建一個(gè)名為  .infinite-list  的  <div>  實(shí)際列表。
    • 使用  :style  綁定屬性  transform  為  getTransform  的值。
    • 使用  v-for  循環(huán)遍歷  visibleData  數(shù)組,并渲染每個(gè)  item
    • 設(shè)置每個(gè)列表項(xiàng)的高度和行高。

2: 腳本部分

  1. 初始化容器和數(shù)據(jù)

    • 使用  ref  獲取列表容器的引用。
    • 初始化狀態(tài)對象  state,包括可視區(qū)高度、偏移量、起始索引和結(jié)束索引。
  2. 計(jì)算可視區(qū)數(shù)據(jù)

    • 計(jì)算可視區(qū)高度和可顯示的數(shù)據(jù)條數(shù)。
    • 通過  slice  方法截取當(dāng)前可視區(qū)域內(nèi)的數(shù)據(jù)片段。
  3. 動(dòng)態(tài)更新列表

    • 在滾動(dòng)事件中實(shí)時(shí)更新起始索引和結(jié)束索引,從而更新當(dāng)前可視區(qū)域的數(shù)據(jù)。
    • 使用  transform  屬性對列表進(jìn)行偏移,確保列表隨用戶的滾動(dòng)而平滑移動(dòng)。
  4. 樣式優(yōu)化

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

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

})

// 可視區(qū)顯示的數(shù)據(jù)條數(shù)

const visibleCount = computed(() => {

  return state.screenHeight / props.itemSize

})

// 可視區(qū)域顯示的真實(shí)數(shù)據(jù)

const visibleData = computed(() => {

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

})

// 當(dāng)前列表總高度

const listHeight = computed(() => {

  return props.listData.length * props.itemSize

})

// list跟著父容器移動(dòng)了,現(xiàn)在列表要移動(dòng)回來

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)

}

  • 導(dǎo)入和定義屬性

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

})

    • 導(dǎo)入必要的Vue Composition API函數(shù)。
    • 定義  props  屬性,包含  listData  和  itemSize
    • 使用  reactive  創(chuàng)建響應(yīng)式狀態(tài)對象  state
  • 計(jì)算屬性

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  計(jì)算可視區(qū)可以顯示的數(shù)據(jù)條數(shù)。
    • visibleData  計(jì)算當(dāng)前可視區(qū)域的實(shí)際數(shù)據(jù)。
    • listHeight  計(jì)算整個(gè)列表的高度。
    • getTransform  計(jì)算列表的偏移量。
  • 引用和生命周期鉤子

const listRef = ref(null)

onMounted(() => {

  state.screenHeight = listRef.value.clientHeight

  state.end = state.start + visibleCount.value

})

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

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  函數(shù)在滾動(dòng)時(shí)更新  startend  和  startOffset

四:總結(jié)

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

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


本文來源于稀土掘金技術(shù)社區(qū),作者:midsummer18

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


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