欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

js前端對于大量數(shù)據(jù)的展示方式及處理方法

 更新時間:2020年12月02日 14:47:31   作者:Xuelulu  
這篇文章主要介紹了js前端對于大量數(shù)據(jù)的展示方式及處理方法,幫助大家更好的理解和使用js,感興趣的朋友可以了解下

最近暫時脫離了演示項目,開始了公司內比較常見的以表單和列表為主的項目。
干一個,愛一個了。從開始的覺得自己都做了炫酷的演示項目了,這對我來說就是個小意思,慢慢也開始踩坑,有了些經(jīng)驗總結可談。

現(xiàn)下不得不說是個數(shù)據(jù)的時代,有數(shù)據(jù)就必定有前端來展示。
雜亂的數(shù)據(jù)通過數(shù)據(jù)分析(未碰到的點,不講請搜),提煉出業(yè)務相關的數(shù)據(jù)維度,而前端所做的就是把這些一個個數(shù)據(jù)通過不同維度(key-value)的描述來展示到頁面上。

除去花哨的展示方式(圖表等),展示普通的大量列表數(shù)據(jù)有兩種常用方式,分頁和觸底加載(滾動加載)。

分頁是一種比較經(jīng)典的展示方式,碰到的問題比較少,最多是因為一頁展示的數(shù)據(jù)量大些的時候可以用圖片懶加載,來加速一些(不過基本一頁也不太會超過200個,不然就失去了分頁的意義了)。

而最近在實現(xiàn)滾動加載時,出現(xiàn)了卡頓的情況。

問題背景:

數(shù)據(jù)量:1500左右;
數(shù)據(jù)描述形式:圖片 + 部分文字描述;
卡頓出現(xiàn)在兩個地方:

滾動卡頓,往往是動一下滾輪,就要卡個2-3s
單個數(shù)據(jù)卡片事件響應卡頓:鼠標浮動,本應0.5s向下延展,但是延展之前也會卡個1-2s;鼠標點擊,本應彈出圖片大圖,但是彈出前也要卡個1-2s

分析過程:

卡頓首先想到是渲染幀被延長了,用控制臺的Performance查看,可以看出是重排重繪費時間:

如圖,Recalculate Style占比遠遠大于其他,一瞬間要渲染太多的卡片節(jié)點,重排重繪的量太大,所以造成了主要的卡頓。
因此,需要減少瞬間的渲染量。

渲染的數(shù)據(jù)項與圖片渲染有關,于是會想到圖片資源的加載和渲染,看控制臺的Network的Img請求中,有大量的pending項(pending項參考下圖所示)。

圖片在不停地加載然后渲染,影響了頁面的正常運行,因此可以作懶加載優(yōu)化。

解決過程:

首先針對最主要的減少瞬間渲染量,逐步由簡入繁嘗試:

1. 自動觸發(fā)的延時渲染
由定時器來操作,setTimeout和setInterval都可以,注意及時跳出循環(huán)即可。
我使用了setTimeout來作為第一次嘗試(下面代碼為后續(xù)補的手寫,大概意思如此)

使用定時器來分頁獲取數(shù)據(jù),然后push進展示的列表數(shù)據(jù)中:

data() {
 return {
  count: -1,
  params: {
   ... // 請求參數(shù)
   pageNo: 0,
   pageSize: 20
  },
  timer:null,
  list: []
 }
},
beforeDestroy() {
 if (this.timer) {
  clearTimeout(this.timer)
  this.timer = null
 }
},
methods: {
 getListData() {
  this.count = -1
  this.params = {
   ... // 請求參數(shù)
   pageNo: 0,
   pageSize: 20
  }
  this.timer = setTimeout(this.getListDataInterval, 1000)
 },
 getListDataInterval() {
  params.pageNo++
  if (params.pageNo === 1) {
   this.list.length = 0
  }
  api(params) // 請求接口
   .then(res => {
    if (res.data) {
     this.count = res.data.count
     this.list.push(...res.data.list)
    }
   })
   .finally(() => {
    if (count >= 0 && this.list.length < count) {
     this.timer = setTimeout(this.getListDataInterval, 1000)
    }
   })
 }
 ...
}

結果:首屏渲染速度變快了,不過滾動和事件響應還是略卡頓。
原因分析:滾動的時候還是有部分數(shù)據(jù)在渲染和加載,其次圖片資源的加載渲染量未變(暫未作圖片懶加載)。

2. 改為滾動觸發(fā)加載(滾動觸發(fā)下的“分頁”形容的是數(shù)據(jù)分批次)

滾動觸發(fā),好處在于只會在觸底的情況下影響用戶一段時間,不會在開始時一直影響用戶,而且觸底也是由用戶操作概率發(fā)生的,相對比下,體驗性增加。
此處有兩種做法:

滾動觸發(fā)“分頁”請求數(shù)據(jù),
缺點:除了第一次,之后每次滾動觸發(fā)展示數(shù)據(jù)會比下一種耗費多一個請求的時間
一次性獲取所有數(shù)據(jù)存在內存中,滾動觸發(fā)“分頁”展示數(shù)據(jù)。
缺點:第一次一次性獲取所有數(shù)據(jù)的時間,比上一種耗費多一點時間
上述兩種做法,可視數(shù)據(jù)的具體數(shù)量決定(據(jù)同事所嘗試,兩三萬個數(shù)據(jù)的獲取時間在1s以上,不過這個也看數(shù)據(jù)結構的復雜程度和后端查數(shù)據(jù)的方式),決定前可以調后端接口試一下時間。

例:結合我本次項目的實際情況,不需要一次性獲取所有的數(shù)據(jù),可以一次性獲取一個時間點的數(shù)據(jù),而每個時間點的數(shù)據(jù)不會超過3600個,這就屬于一個比較小的量,嘗試下來一次性獲取的時間基本不超過500ms,于是我選擇第二種

先一次性獲取所有數(shù)據(jù),由前端控制滾動到距離底部的一定距離,push一定量的數(shù)據(jù)到展示列表數(shù)據(jù)中:

data() {
 return {
  timer: null,
  list: [], // 存儲數(shù)據(jù)的列表
  showList: [], // html中展示的列表
  isLoading: false, // 控制滾動加載
  currentPage: 1, // 前端分批次擺放數(shù)據(jù)
  currentPageSize: 50, // 前端分批次擺放數(shù)據(jù)
  lastListIndex: 0, // 記錄當前獲取到的最新數(shù)據(jù)位置
  lastTimeIndex: 0, // 記錄當前獲取到的最新數(shù)據(jù)位置
 }
},
created() { // 優(yōu)化點:可做可不做,其中的數(shù)值都是按照卡片的寬高直接寫入的,因為不是通用組件,所以從簡。
 this.currentPageSize = Math.round(
  (((window.innerHeight / 190) * (window.innerWidth - 278 - 254)) / 220) * 3
 ) // (((window.innerHeight / 卡片高度和豎向間距) * (window.innerWidth - 列表內容距視口左右的總距離 - 卡片寬度和橫向間距)) / 卡片寬度) * 3
// *3代表我希望每次加載至少能多出三個視口高度的數(shù)據(jù);列表內容距視口左右的總距離:是因為我是兩邊固定寬度,中間適應展示內容的結構
},
beforeDestroy() {
 if (this.timer) {
  clearTimeout(this.timer)
  this.timer = null
 }
},
methods: {
 /**
  * @description: 獲取時間點的數(shù)據(jù)
  */
 getTimelineData(listIndex, timeIndex) {
  if (
   // this.list的第一、二層是時間軸this.list[listIdex].timeLines[timeIndex],在獲取時間點數(shù)據(jù)之前獲取了
   this.list &&
   this.list[listIndex] &&
   this.list[listIndex].timeLines &&
   this.list[listIndex].timeLines[timeIndex] &&
   this.showList &&
   this.showList[listIndex] &&
   this.showList[listIndex].timeLines &&
   this.showList[listIndex].timeLines[timeIndex]
  ) {
   this.isLoading = true
   // 把當前時間點變成展示狀態(tài)
   if (!this.showList[listIndex].active) {
    this.handleTimeClick(listIndex, this.showList[listIndex])
   }
   if (!this.showList[listIndex].timeLines[timeIndex].active)
    this.handleTimeClick(
     listIndex,
     this.showList[listIndex].timeLines[timeIndex]
    )
   if (!this.list[listIndex].timeLines[timeIndex].snapDetailList) {
    this.currentPage = 1
   }
   if (
    !this.list[listIndex].timeLines[timeIndex].snapDetailList // 第一次加載時間點數(shù)據(jù),后面的或條件可省略
   ) {
    
    return suspectSnapRecords({
     ...
    })
     .then(res => {
      if (res.data && res.data.list && res.data.list.length) {
       let show = []
       res.data.list.forEach((item, index) => {
        show[index] = {}
        if (index < 50) {
         show[index].show = true
        } else {
         show[index].show = true
        }
       })
       this.$set(
        this.list[listIndex].timeLines[timeIndex],
        'snapDetailList',
        res.data.list
       )
       this.$set(
        this.showList[listIndex].timeLines[timeIndex],
        'snapDetailList',
        res.data.list.slice(0, this.currentPageSize)
       )
       this.$set(
        this.showList[listIndex].timeLines[timeIndex],
        'showList',
        show
       )
       this.currentPage++
       this.lastListIndex = listIndex
       this.lastTimeIndex = timeIndex
      }
     })
     .finally(() => {
      this.$nextTick(() => {
       this.isLoading = false
      })
     })
   } else { // 此處是時間點被手動關閉,手動關閉會把showList中的數(shù)據(jù)清空,但是已經(jīng)加載過數(shù)據(jù)的情況
    if (
     this.showList[listIndex].timeLines[timeIndex].snapDetailList
      .length === 0
    ) {
     this.currentPage = 1
     this.lastListIndex = listIndex
     this.lastTimeIndex = timeIndex
    }
    this.showList[listIndex].timeLines[timeIndex].snapDetailList.push(
     ...this.list[listIndex].timeLines[timeIndex].snapDetailList.slice(
      (this.currentPage - 1) * this.currentPageSize,
      this.currentPage * this.currentPageSize
     )
    )
    this.currentPage++
    this.$nextTick(() => {
     this.isLoading = false
    })
    return
   }
  } else {
   return
  }
 },
 /**
  * @description: 頁面滾動監(jiān)聽,用的是公司內部的框架,就不展示html了,不同框架原理都是一樣的,只是需要寫的代碼多與少的區(qū)別,如ElementUI的InfiniteScroll,可以直接設置觸發(fā)加載的距離閾值
  */
 handleScroll({ scrollTop, percentY }) { // 此處的scrollTop是組件返回的縱向滾動的已滾動距離,percentY則是已滾動百分比
   this.bus.$emit('scroll') // 觸發(fā)全局的滾動監(jiān)聽,用于圖片的懶加載
   this.scrolling = true
   if (this.timer) { // 防抖機制,直至滾動停止才會運行定時器內部內容
    clearTimeout(this.timer)
   }
   this.timer = setTimeout(() => {
    requestAnimationFrame(async () => {
     // 因為內部有觸發(fā)重排重繪,所以把代碼放在requestAnimationFrame中執(zhí)行
     let height = window.innerHeight
     if (
      percentY > 0.7 && // 保證最開始的時候不要瘋狂加載,已滾動70%再加載
      Math.round(scrollTop / percentY) - scrollTop < height * 2 && // 保證數(shù)據(jù)量大后滾動頁面長的時候不要瘋狂加載,在觸底小于兩倍視口高度的時候才加載
      !this.isLoading // 保險,不同時運行下面代碼,以防運行時間大于定時時間
     ) {
      this.isLoading = true
      let len = this.list[this.lastListIndex].timeLines[
       this.lastTimeIndex
      ].snapDetailList.length // list為一次性獲取所有數(shù)據(jù)存在內存中
      if ((this.currentPage - 1) * this.currentPageSize < len) { // 前端分批次展示的情況
       this.showList[this.lastListIndex].timeLines[
        this.lastTimeIndex
       ].snapDetailList.push(
        ...this.list[this.lastListIndex].timeLines[
         this.lastTimeIndex
        ].snapDetailList.slice(
         (this.currentPage - 1) * this.currentPageSize,
         this.currentPage * this.currentPageSize
        )
       )
       this.currentPage++
      } else if (
       this.list[this.lastListIndex].timeLines.length >
       this.lastTimeIndex + 1
      ) { // 前端分批次展示完上一波數(shù)據(jù),該月份時間軸上下一個時間點存在的情況
       await this.getTimelineData(
        this.lastListIndex,
        this.lastTimeIndex + 1
       )
      } else if (this.list.length > this.lastTimeIndex + 1) { // 前端分批次展示完上一波數(shù)據(jù),該月份時間軸上下一個時間點不存在,下一個月份存在的情況
       await this.getTimelineData(this.lastListIndex + 1, 0)
      }
     }
     this.$nextTick(() => {
      this.isLoading = false
      this.scrolling = false
     })
    })
   }, 500)
  },

結果:首屏渲染和事件響應都變快了,只是滑動到底部的時候有些許卡頓。
原因分析:滑動到底部的卡頓,也是因為一瞬間渲染一堆數(shù)據(jù),雖然比一次性展示所有的速度快很多,但是還是存在相比一次性展示不那么嚴重的重排和重繪,以及圖片不停加載渲染的情況。

3. 滾動觸發(fā)+圖片懶加載

圖片懶加載可以解決每次渲染數(shù)據(jù)的時候因為圖片按加載順序不停渲染產(chǎn)生的卡頓。
滾動觸發(fā)使用點2的代碼。
提取通用的圖片組件,通過滾動事件的全局觸發(fā),來控制每個數(shù)據(jù)項圖片的加載:
如上,點2中已經(jīng)在handleScroll中設置了 this.bus.$emit('scroll') // 觸發(fā)全局的滾動監(jiān)聽,用于圖片的懶加載

// main.js
Vue.prototype.bus = new Vue()
...

以下的在template中寫js不要學噢

// components/DefaultImage.vue
<template>
 <div class="default-image" ref="image">
  <img src="@/assets/images/image_empty.png" v-if="imageLoading" />
  <img
   class="image"
   v-if="showSrc"
   v-show="!imageLoading && !imageError"
   :src="showSrc"
   @load="imageLoading = false"
   @error="
    imageLoading = false
    imageError = true
   "
  />
  <img src="@/assets/images/image_error.png" v-if="imageError" />
 </div>
</template>
<script>
export default {
 name: 'DefaultImage',
 props: {
  src: String, // 圖片源
  lazy: Boolean // 懶加載
 },
 data() {
  return {
   imageLoading: true,
   imageError: false,
   showSrc: '', // 渲染的src
   timer: null
  }
 },
 mounted() {
  if (this.lazy) {
   this.$nextTick(() => {
    this.isShowImage()
   })
   this.bus.$on('scroll', this.handleScroll)
  } else {
   this.showSrc = this.src
  }
 },
 beforeDestroy() {
  if (this.lazy) {
   this.bus.$off('scroll', this.handleScroll)
  }
  if (this.timer) {
   clearTimeout(this.timer)
   this.timer = null
  }
 },
 methods: {
  handleScroll() {
   if (this.timer) {
    clearTimeout(this.timer)
   }
   this.timer = setTimeout(this.isShowImage, 300)
  },
  isShowImage() {
   let image = this.$refs.image
   if (image) {
    let rect = image.getBoundingClientRect()
    const yInView = rect.top < window.innerHeight && rect.bottom > 0
    const xInView = rect.left < window.innerWidth && rect.right > 0
    if (yInView && xInView) {
     this.showSrc = this.src
     this.bus.$off('scroll', this.handleScroll)
    }
   }
  }
 }
}
</script>

結果:在點2首屏展示快的基礎上,事件交互更快了,觸發(fā)展示數(shù)據(jù)也快了。
原因分析:防抖的圖片懶加載之后,只在用戶滾動停止時,加載視口內的圖片,就沒有后續(xù)不斷的加載渲染圖片,也就不會因為不停渲染圖片而影響事件交互和基礎的無圖卡片渲染。

以上一頓操作之后已經(jīng)符合本項目的需求了。
不過我研究了一下進階操作 🤔
還可以只渲染視口元素,非視口用padding代替,以及把計算過程放在Web Worker多線程執(zhí)行,進一步提升速度。
待我研究一下操作補上

以上就是js前端對于大量數(shù)據(jù)的展示方式及處理方法的詳細內容,更多關于js 大量數(shù)據(jù)展示及處理的資料請關注腳本之家其它相關文章!

相關文章

  • javascript中全局對象的parseInt()方法使用介紹

    javascript中全局對象的parseInt()方法使用介紹

    全局對象的parseInt()方法該如何使用,下面為大家詳細介紹下,感興趣的朋友不要錯過
    2013-12-12
  • javascript檢測對象中是否存在某個屬性判斷方法小結

    javascript檢測對象中是否存在某個屬性判斷方法小結

    檢測對象中屬性的存在與否可以通過以下幾種方法來判斷:使用in關鍵字、使用對象的hasOwnProperty()方法、用undefined判斷、在條件語句中直接判斷,感興趣的朋友可以了解下哈
    2013-05-05
  • JS實現(xiàn)密碼框效果

    JS實現(xiàn)密碼框效果

    這篇文章主要為大家詳細介紹了JS實現(xiàn)密碼框效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • 一文教會你解決js數(shù)字精度丟失問題

    一文教會你解決js數(shù)字精度丟失問題

    在JavaScript中計算兩個十進制數(shù)的和,有時候會出現(xiàn)令人驚訝的結果,相信這個大家也都知道了,下面這篇文章主要給大家介紹了關于解決js數(shù)字精度丟失問題的相關資料,需要的朋友可以參考下
    2022-08-08
  • Python版實現(xiàn)微信公眾號掃碼登陸

    Python版實現(xiàn)微信公眾號掃碼登陸

    這篇文章主要介紹了Python版實現(xiàn)微信公眾號掃碼登陸,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • HTML顏色選擇器實現(xiàn)代碼

    HTML顏色選擇器實現(xiàn)代碼

    HTML顏色選擇器實現(xiàn)代碼需要的朋友可以參考下。
    2010-11-11
  • JavaScript仿小米輪播圖效果

    JavaScript仿小米輪播圖效果

    這篇文章主要為大家詳細介紹了JavaScript仿小米輪播圖效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • 小程序實現(xiàn)跑馬燈效果

    小程序實現(xiàn)跑馬燈效果

    這篇文章主要為大家詳細介紹了小程序實現(xiàn)跑馬燈效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • 一個JavaScript遞歸實現(xiàn)反轉數(shù)組字符串的實例

    一個JavaScript遞歸實現(xiàn)反轉數(shù)組字符串的實例

    這篇文章主要介紹了一個JavaScript遞歸實現(xiàn)反轉數(shù)組字符串的實例,很不錯,非常適合新手朋友們
    2014-10-10
  • 詳解Webpack4多頁應用打包方案

    詳解Webpack4多頁應用打包方案

    這篇文章主要介紹了詳解Webpack4多頁應用打包方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07

最新評論