在開發完成進行簡單效能測試時,發現列表數量達到數百條後,切換檢視就會造成明顯的頁面卡頓,使用者體驗很差。於是著手進行效能最佳化。
第一次最佳化:解決已知問題由於專案是使用 Vue.js (以下簡稱 "Vue")來實現,所以首先檢視 Vue 是否存在效能瓶頸,如果存在則考慮替換 Vue 進行最佳化。
透過檢視官方給出的benchmark結果,我們可以得知 Vue 的列表渲染效能在高亮和交換列表元素的時候效能較差,在建立列表和新增列表元素的時候效能都是不錯的,執行時間在毫秒級別。
既然 Vue 並沒有給我們制定太低的效能天花板,那麼我們可以在使用 Vue 的基礎上繼續進行效能最佳化。
<section class="file-card-list" v-if="cViewType == 'card'"> ......</section><section class="file-line-list" v-if="cViewType == 'line'"> ......</section>
這裡透過 v-if 指令來實現列表切換,每次切換時都會銷燬之前的檢視列表,然後建立一個新的檢視列表。在列表元素非常多時,會造成大量的 DOM 元素建立和銷燬,效能開銷是很昂貴的。
所以進行最佳化的最簡單方式就是快取已經渲染的列表。對應到程式碼也很簡單,就是將 v-if 改為 v-show,這樣就可以透過 CSS 來控制兩個列表的顯示/隱藏,從而避免 DOM 元素的重複建立。
改動之後效果確實也非常明顯,事情似乎到此結束,但如果列表數量增加到一兩個數量級,比如到達一萬,是否仍舊流暢呢?
第二次最佳化:排查可能的問題當我將列表元素數量增加到一萬之後,卡頓問題果然再次出現了。
而瀏覽器頁面卡頓無外乎兩個原因,要麼指令碼引擎在執行 js 程式碼,要麼渲染引擎在渲染頁面。
由於前面已經對指令碼引擎執行 js 程式碼的問題進行過最佳化,這一次我們將最佳化方向轉向渲染引擎。
渲染引擎程式需要藉助 CPU 來執行渲染操作,而 CPU 本身並不擅長於處理批次圖形渲染,所以可以把這部分的渲染工作交給 GPU。
透過設定 CSS 樣式就可以呼叫 GPU,下面是一種實現方式。
首先將兩個檢視列表都設定為絕對定位,脫離文件流。然後在點選事件中動態修改檢視列表的 z-index 屬性,控制兩個列表的層疊關係,透過讓一個列表覆蓋另一個列表來實現顯示/隱藏效果。這裡需要注意的是,雖然只要讓卡片列表脫離文件流就可以達到效果,但由於條目列表高度超過卡片列表,導致在顯示卡片列表時底部仍然出現條目列表元素,所以將兩個元素都設定為絕對定位,並且讓其擁有各自獨立的捲軸。
最佳化之後,萬張圖片可以實現毫秒級切換,非常順滑。
第三次最佳化:思考方案的副作用世上沒有銀彈,即使借用GPU來加速渲染仍會產生一些副作用。
由於 GPU 對渲染圖形數量不敏感而對渲染次數敏感,而瀏覽器對請求的併發數(一次併發8~6個請求)又有限制,這在一定程度上會增加 GPU 的渲染次數,從而影響使用者體驗。
對於這個問題可以透過 HTTP/2 協議提升併發能力或者採用分批預載入的方式(等一批圖片資源都預載入完再更新到卡片列表)。
由於這些方案還沒有來得及實踐,這裡就不具體展開了~
總結針對不同效能問題的場景,最佳化的手段很多,總體上我們只需要抓住兩條線索就能找到合適的解決方案,分別是:做減法和做除法。
做減法就是直接減少操作步驟或資源大小,比如第一次最佳化中透過 v-show 來進行快取,就是減少建立列表的操作。做除法就是對耗時的操作進行拆分。比如第二次最佳化中,耗時的渲染操作轉交給 GPU 來執行。