這幾個月接了一個日誌收集系統的活,由於這個系統是屬於傳承的項目,因此我也想在系統上作一些標誌性的改動,做爲接力棒傳遞下去。前端
這個日誌系統從前端到服務端,都作了不小的改動,好比虛擬列表,electron熱更新,sql優化,增長了用戶的概念,也就是須要登陸,把數據庫升級爲Elasticsearch,等等。我以後會把一些我感受比較好玩的東西分享出來。vue
我最早操刀修改的是日誌list的展現。這個list就是比較常規的堆節點,頁面會隨着時間的增長致使DOM節點愈來愈多,這是一個不得不改的問題。ajax
首先,由於同事們對這個列表的長時間的使用已經習慣了,因此最好在體驗上不要進行大的修改。爲此,我須要把以前大概的功能列舉出來:sql
總結完以前的功能以後,我須要再梳理一下個人需求:數據庫
梳理完成以後,通過考慮我決定使用虛擬列表來代替現有的長列表,這也是踩坑之路的開始。數組
可能以前你們多多少少會據說過這個概念甚至開發過虛擬列表,我無論。在開始以前我仍是先和你們捋一下概念。(敲黑板!!!)瀏覽器
咱們知道,在瀏覽器渲染頁面的時候,當DOM節點的數量越多,每一次重繪的時候,對性能的影響也就越大。緩存
假如咱們須要展現一個信息量很大,大約有數十萬條數據。遇到這樣子的狀況,其實如今有許多的方案,咱們最多見的方案就相似PC上的下一頁、上一頁,可是這個方案在體驗上其實並不友好。大部分的用戶會比較喜歡不停的向下滾動就能夠看到新的內容,可是這個就會遇到一個問題,不停的加載數據,致使頁面堆積的節點愈來愈多,所消耗的內存不斷增大,最後連滾動都會卡頓。bash
這時候咱們從新分析一下,就會發現其實有不少數據咱們大多數狀況下是不須要看見的,若是隻考慮咱們能看到數據的話,其實須要渲染的數據量就會很是的少了,很好的提升了渲染的效率,減小由於大量的重繪照成沒必要要的影響。微信
這麼一梳理一下,答案簡直呼之欲出----虛擬列表。
虛擬列表其實沒有什麼特別神奇的地方,說白了就是一種展現列表的思路,在頁面上建立一個容器做爲可視區,在這個可視區內展現長列表中的一部分,也就是在可視區渲染列表。
如圖中所示,是一個簡單的虛擬列表的模型,圖中有幾個概念須要你們稍微瞭解一下:
可視區你們能夠這麼理解,咱們如今有一個<div class="show-box">
,給這個元素加一些樣式。
.show-box{
width: 375px;
height: 500px;
margin: 0 auto;
position: relative;
}
複製代碼
經過這個樣式咱們能夠看出這個可視區容器的高度爲500px
。
真實列表就是會被渲染出來的列表,這麼說可能不太理解,舉個栗子:如今須要被渲染出來的列表數量一共有1000
條,可是實際上在頁面須要被渲染的列表數量(須要被看到的數據)只須要100
條,這個100
條就是所謂的真實列表。
<div class="list-body-box" @scroll="listScroll"> ----- 真實列表
<div class="list-body"> ------ 載體
</div>
</div>
-------------------------- style --------------------------------
.list-body {
min-height: 10px;
position: absolute;
width: 100%;
}
複製代碼
在這裏,建議真實列表的長度須要比可視區的高度長一些,有一個滾動條的話,以後能夠經過scroll
監聽作一些其餘的操做。
可能有一個點須要和你們解釋一下爲何個人<div class="list-body">
是絕對定位。
當你的某一個元素會頻繁發生變化的時候,最好將這個模塊經過絕對定位的方式,脫離文檔流,能夠減小重繪帶來的影響。
咱們先看一下瀏覽器的渲染機制
絕對定位或者浮動脫離了正常的文檔流,至關於只是在節點上存放了一個token,而後經過這個token去進行映射,因此若是你採用了絕對定位的方法,也只會對這一塊元素進行重繪。
以前也說到了,真實列表實際上只是總列表其中很小的一部分,在這以外還有不少列表須要被渲染。所以,你們能夠把真實列表理解爲一個片斷。被渲染的第一個元素的index
就是片斷中第一個元素在總列表中的位置,也就是數組中的index
。
舉個栗子:個人總列表(數組)的長度爲1000
,而須要渲染的列表片斷爲100—200
,那麼這個開始的位置,也就是數組的index
則爲99
。
解釋同上,最後一個元素的index
是199
。
這裏要提一下,個人框架用的是vue,因此虛擬列表的實現也是比較方便的。
<div class="list-body-box">
<div class="list-body">
<div
v-for="(item, idx) in list"
v-if="idx >= startIdx && idx <= endIdx"
:key="idx"
class="list-row">
<div class="col-item col-1">{{item.col_1}}</div>
<div class="col-item col-2">{{item.col_2}}</div>
<div class="col-item col-3">{{item.col_3}}</div>
<div class="col-item col-4">{{item.col_4}}</div>
<div class="col-item col-5">{{item.col_5}}</div>
<div class="col-item col-6">{{item.col_6}}</div>
<div class="col-item col-7">{{item.col_7}}</div>
</div>
</div>
</div>
複製代碼
模板上,沒有什麼太特別的地方,主要就是經過v-if
去控制列表的展現,經過startIdx
和endIdx
的增減,去展現不一樣位置的數據,讓這兩個值遞增就能夠實現列表滾動。
下邊咱們會說一下自動滾動在代碼上的實現,主要是經過一個主動的事件去頻繁的觸發對startIdx
和endIdx
遞增或者遞減。
let time = null;
...
autoScroll(){
time = setTimeout(()=>{
let listLen = this.list.length - 1;
this.endIdx = listLen;
this.startIdx = (listLen + 1) <= 100 ? 0 : listLen - 100;
this.autoScroll();
},300);
}
複製代碼
如上代碼所示,我只須要再讓一個方法去觸發autoScroll()
,這個方法就會在setTimeout
的做用下自調用,startIdx
和endIdx
會不斷遞增列表就能夠自動滾動了,在這裏邊有一個表達式
this.startIdx = (listLen + 1) <= 100 ? 0 : listLen - 100;
複製代碼
這一塊的話主要是解決當頁面剛打開或者清空列表的時候,實際上列表的長度比較短,是不須要進行滾動的,換句話說,startIndex
須要在列表總長度在到達一個值以前一直爲0
。
到這裏,簡單的虛擬列表就實現了。
咱們使用的是WebSocket來傳遞數據,數據量很多。所以極可能會出現過於頻繁更新數據的狀況,數據一更新,頁面也會隨之改變,這樣會對性能照成必定的影響。因此咱們須要對這個頻度進行把控。目前的方案是加一個緩衝池。
這緩衝池的思路大概是這樣的,WebSocket傳遞數據的時候,咱們把這段時間的數據先存在一個數組中,而後每隔一段時間,好比500ms,再把數據push到完整的列表中,這個方案可能就會涉及到節流。let socketPool = []; //存儲一段時間的數據
let socketTimer;
socketFun( (data) => {
//先製造一個緩存區間,用來作緩存socket的數據
socketPool.push(data);
//每次都把當前的數據進行push到list
if(!socketTimer){
socketTimer = setTimeout(()=>{
this.appendRecord(socketPool);
socketPool.length = 0;
this.scrollToBottom();
socketTimer = null;
},500);
}
});
複製代碼
在這裏邊appendRecord()
是用來處理數據,而且把數據放入list中的方法,而scrollToBottom()
就是爲了當數據push到list以後,列表能直接展現最新的數據,也就是讓頁面滾動到列表的最底部。
緩衝池其實也是提高性能的一個方案,這個方案最核心的地方就是減小頁面渲染的次數。你們能夠這麼理解:每秒鐘可能會有10條數據須要被渲染,假如我每次都老老實實的渲染,那麼10秒的時間我就要渲染10次,實際上是沒有必要的,所以咱們能夠考慮每2秒渲染一次,這樣10s的時間內個人渲染次數就會減小到5次。你能夠理解爲性能提高了一倍。
按照以前的需求,當用戶點擊列表中的其中一條數據的時候,列表是須要中止滾動的。因此我加了一個滾動鎖autoScrollLoack
,這個鎖的做用就是當我點擊到列表中的某一條的時候,執行autoScrollLoack = true
頁面就不會滾動了。這個鎖的判斷會放在this.scrollToBottom()
中,代碼你們稍微看看就行。
scrollToBottom(){
if(autoScrollLoack){
return;
}
...do something
},
複製代碼
這個autoScrollLoack
在頁面中會與一個單選框進行雙向綁定,所以用戶就能夠經過改變單選框的選中狀態來控制鎖的狀態,其實在有了這個鎖以後,頁面若是由於需求中止滾動了,用戶也能有所感知,不至於忽然滾動就中止了,看起來像個bug。
聚焦移動的功能以前需求也說過了,就是選中了一條信息,能夠經過上下鍵將聚焦指向上一個或者下一個,這個其實也比較好實現
<div
v-for="(item, idx) in list"
v-if="idx >= startIdx && idx <= endIdx"
:key="item.id"
:class="{'active':curIdx==idx}"
class="list-row"
@click="showDetail(item.id)">
複製代碼
在這裏,你們能夠看到,active
就是聚焦的時候列表的樣式。在邏輯上,把當前選中項的index
賦給curIndex
,前端模板上經過vue對class的綁定來控制樣式,判斷條件就是curIndex == index
。
聚焦功能已經實現了,那麼接下來要實現經過鍵盤中的上下鍵,實現移動聚焦的效果。這個功能很簡單,咱們徹底能夠經過vue提供的監聽事件來實現,具體的實現你們能夠在官網上搜一下keyup
。
<div class="list-body-box" @scroll="listScroll" @keyup="moveFocus">
...
...
moveFocus(e){
let keyCode = Number(e.keyCode);
switch(keyCode){
case 38:
this.curIdx -= 1;
this.showDetail();
break;
case 40:
this.curIdx += 1;
this.showDetail();
break;
}
},
複製代碼
這段代碼實現了聚焦的上下挪動。根據需求咱們每一次聚焦的時候須要展現聚焦項對應的日誌詳情,詳情是須要發ajax請求來獲取的。問題來了,有一個場景:我想經過鍵盤把當前的聚焦向下挪動10次,在不停聚焦的過程當中我會觸發10次請求,這個其實不必,我在快速移動的過程當中,是不care
詳情的,我只須要展現目標詳情就好了。綜上,咱們須要再加一個防抖。
let detailTimer;
showDetail(id){
if(detailTimer){
clearTimeout(detailTimer);
}
detailTimer = setTimeOut(() => {
$.post('...',{
id:id
}).then((res) => {
do something...
});
},300);
}
複製代碼
從上邊的邏輯咱們能夠看出來,當用戶在快速挪動聚焦的時候是不會觸發請求的,實際上這個改動很大程度上提高了用戶的流暢度。
其實虛擬列表的開發仍是比較簡單的,可是實際意義卻比較大,在這個過程當中會涉及到很多頁面的優化,感興趣的童鞋能夠嘗試一下,歡迎你們加我微信交流:zyf348519452,咱們下期見~