收藏這些vue專案效能最佳化方式,總有一天能用上!

Weiwen發表於2022-05-05

vue專案如何進行效能最佳化?下面本篇文章給大家分享一些vue專案一定會用到的效能最佳化方法,希望對大家有所幫助!

提起效能最佳化 很多人眼前浮現的面試經驗是不是歷歷在目呢?反正,效能最佳化在我看來他永遠是前端領域的熱度之王

而本渣最近維護的專案恰巧在這個方向下了很大功夫,一些經驗之談奉上,希望對大家有些許幫助!

效能最佳化標準


既然說效能最佳化,那他總得有一個公認的標準,這就是我們很多次聽到的Lighthouse

在很多單位,都有著自己的效能監控平臺,我們只需要引入相應的sdk,那麼在平臺上就能分析出你頁面的存在的效能問題,大家是不是學的很神奇!

其實除了苛刻的業務,需要特殊的定製,大多數的情況下我們單位的效能最佳化平臺本質上其實就是利用無頭瀏覽器(Puppeteer)跑Lighthouse

理解了我們單位的效能監控平臺的原理之後,我們就能針對性的做效能最佳化,也就是面向Lighthouse程式設計

Lighthouse

lighthouse 是 Google Chrome 推出的一款開源自動化工具,它可以蒐集多個現代網頁效能指標,分析 Web 應用的效能並生成報告,為開發人員進行效能最佳化的提供了參考方向。

說起Lighthouse在現代的谷歌瀏覽器中業已經整合

他可以分析出我們的頁面效能,透過幾個指標

Lighthouse 會衡量以下效能指標項:

  • 首次內容繪製(First Contentful Paint)。即瀏覽器首次將任意內容(如文字、影像、canvas 等)繪製到螢幕上的時間點。

  • 可互動時間(Time to Interactive)。指的是所有的頁面內容都已經成功載入,且能夠快速地對使用者的操作做出反應的時間點。

  • 速度指標(Speed Index)。衡量了首屏可見內容繪製在螢幕上的速度。在首次載入頁面的過程中儘量展現更多的內容,往往能給使用者帶來更好的體驗,所以速度指標的值約小越好。

  • 總阻塞時間(Total Blocking Time)。指First Contentful Paint 首次內容繪製 (FCP)與Time to Interactive 可互動時間 (TTI)之間的總時間

  • 最大內容繪製(Largest Contentful Paint)。度量標準報告視口內可見的最大影像或文字塊的呈現時間

  • 累積佈局偏移(# Cumulative Layout Shift)。衡量的是頁面整個生命週期中每次元素髮生的非預期佈局偏移得分的總和。每次可視元素在兩次渲染幀中的起始位置不同時,就說是發生了LS(Layout Shift)。

在一般情況下,據我的經驗,由於效能監控平臺的和本地平臺的差異,本地可能要達到70分,線上才有可能達到及格的狀態,如果有效能最佳化的需求時,大家酌情處理即可(不過本人覺得,及格即可, 畢竟大學考試有曰:60分萬歲,61分浪費,傳承不能丟,我們們要把更多的時間,放到更重要的事情上來!)

通用常規最佳化手段


lighthouse的的牛x之處就是它能找出你頁面中的一些常規的效能瓶頸,並提出最佳化建議,比如:

於是針對這些最佳化建議,我們需要做一些常規的最佳化:

  • 減少未使用的javascript

  • 移出阻塞渲染的資源

  • 圖片質量壓縮

  • 限制使用字型數量,儘可能少使用變體

  • 最佳化關鍵渲染路徑:只載入當前頁面渲染所需的必要資源,將次要資源放在頁面渲染完成後載入

通用效能最佳化分析


我們知道lighthouse 中有六個效能指標,而在這六個指標中,LCP、 FCP、speed index、 這三個指數尤為重要,因為在一般情況下 這個三個指標會影響 TTI、TBT、CLS 的分數

所以在我們在最佳化時, 需要提高LCP、 FCP和speedIndex 的分數,經過測試, 即使是空頁面也會有時間上的損耗, 初始分數基本都是0.8

注意: 需要值得大家注意的是,我們當前所有測試全部建立在,移動端(之所以用移動端,是由於pc 的強大算力,很少有效能瓶頸)的基礎上,並且頁面上必須有一下內容,才能得出分數,內容必須包括一下的一種或者多種

  • 內嵌在svg元素內的image元素
  • video元素(使用封面影像)
  • 透過url()函式(而非使用CSS 漸變)載入的帶有背景影像的元素
  • 包含文字節點或其他行內級文字元素子元素的塊級元素

否則就會有如下錯誤

接下來我們就從LCP、 FCP和speedIndex 這三個指標入手

FCP(First Contentful Paint)

顧名思義就是首次內容繪製,也就是頁面最開始繪製內容的時間,但是由於我們現在開發的頁面都是spa應用,所以,框架層面的初始化是一定會有一定的效能損耗的,以vue-cli 搭建的腳手架為例,當我初始化空的腳手架,打包後上傳cdn部署,FCP 就會從0.8s提上到1.5秒,由此可見vue 的diff 也不是免費的他也會有效能上的損耗。(學習影片分享:vuejs教程

在最佳化頁面的內容之前我們宣告三個前提

  • 提高FCP的時間其實就是在最佳化關鍵渲染路徑

  • 如果它是一個樣式檔案(CSS檔案),瀏覽器就必須在渲染頁面之前完全解析它(這就是為什麼說CSS具有渲染阻礙性)

  • 如果它是一個指令碼檔案(JavaScript檔案),瀏覽器必須: 停止解析,下載指令碼,並執行它。只有在這之後,它才能繼續解析,因為 JavaScript 指令碼可以改變頁面內容(特別是HTML)。(這就是為什麼說JavaScript阻塞解析)

針對以上的用例測試,我們發現,無論我們怎麼最佳化,框架本身的效能損耗是無法抹除的,我們唯一能做的就是讓框架更早的去執行初始化,並且初始化更少的內容,可做的最佳化手段如下:

  • 所有初始化用不到的js 檔案全部走非同步載入,也就是加上defer或者asnyc ,並且一些需要走cdn的第三方外掛需要放在頁面底部(因為放在頂部,他的解析會阻止html 的解析,從而影響css 等檔案的下載,這也是雅虎軍規的一條)

  • js 檔案拆包,以vue-cli 為例,一般情況下我們可以透過cli的配置 splitChunks 做程式碼分割,將一些第三方的包走cdn,或者拆包。如果有路由的情況下將路由做拆包處理,保證每個路由只載入當前路由對應的js程式碼

  • 最佳化檔案大小 減少字型包、css檔案、以及js檔案的大小(當然這些 腳手架預設都已經做了)

  • 最佳化專案結構,每個元件的初始化都是有效能損耗的,在在保證可維護性的基礎上,儘量減少初始化元件的載入數量

  • 網路協議層面的最佳化,這個最佳化手段需要服務端配合純前端已經無法達到,在現在雲伺服器盛行的時代,自家單位一般都會預設在雲伺服器中開啟這些最佳化手段,比如開啟gzip,使用cdn 等等

其實說來說去,提高FCP 的核心只有理念之後兩個 減少初始化檢視內容減少初始化下載資源大小

LCP(Largest Contentful Paint)

顧名思義就是最大內容繪製, 何時報告LCP,官方是這樣說的

為了應對這種潛在的變化,瀏覽器會在繪製第一幀後立即分發一個largest-contentful-paint型別的PerformanceEntry,用於識別最大內容元素。但是,在渲染後續幀之後,瀏覽器會在最大內容元素髮生變化時分發另一個PerformanceEntry

例如,在一個帶有文字和首圖的網頁上,瀏覽器最初可能只渲染文字部分,並在此期間分發一個largest-contentful-paint條目,其element屬性通常會引用一個<p><h1> 。隨後,一旦首圖完成載入,瀏覽器就會分發第二個largest-contentful-paint條目,其element屬性將引用<img>

需要注意的是,一個元素只有在渲染完成並且對使用者可見後才能被視為最大內容元素。尚未載入的影像不會被視為”渲染完成”。 在字型阻塞期使用網頁字型的文字節點亦是如此。在這種情況下,較小的元素可能會被報告為最大內容元素,但一旦更大的元素完成渲染,就會透過另一個PerformanceEntry物件進行報告。

其實用大白話解釋就是,通常情況下,圖片、影片以及大量文字繪製完成後就會報告LCP

理解了這一點,的最佳化手段就明確了,儘量減少這些資源的大小就可以了,經過測試,減少首屏渲染的圖片以及影片內容大小後,整體分數顯著提高,提供一些最佳化方法:

  • 本地圖片可以使用線上壓縮工具自己壓縮 推薦:https://tinypng.com/

  • 介面中附帶圖片,一般情況下單位中都有對應的oss或者cdn傳參配置透過位址列傳參方式控制圖片質量

  • 圖片懶載入

SpeedIndex(速度指數)

Speed Index採用可視頁面載入的視覺進度,計算內容繪製速度的總分。為此,首先需要能夠計算在頁面載入期間,各個時間點“完成”了多少部分。在WebPagetest中,透過捕獲在瀏覽器中載入頁面的影片並檢查每個影片幀(在啟用影片捕獲的測試中,每秒10幀)來完成的,這個演算法在下面有描述,但現在假設我們可以為每個影片幀分配一個完整的百分比(在每個幀下顯示的數字)

以上是官方解釋的計算方式,其實通俗的將,所謂速度指數就是衡量頁面內容填充的速度

一圖勝千言

經過測試,跟LCP相同,圖片以及影片內容對於SpeedIndex的影響巨大,所有最佳化方向,通之前一致,總的來說,只要提高LCP 以及FCP 的時間SpeedIndex 的時間就會有顯著提高

不過需要注意的是,介面的速度也會影響SpeedIndex的時間,由於AJAX流行的今天,我們大多數的資料都是使用介面拉取。如果介面速度過慢,他就會影響你頁面的初始渲染, 導致效能問題,所以,在做效能最佳化的同時,請求後端夥伴協助,也是效能最佳化的一個方案

排查效能瓶頸

上述分析,根據三個指標提供了一些常規的最佳化手段,那麼在這些最佳化手段中,有的你可以立馬排查到,並且最佳化例如:

  • 最佳化影像,最佳化字型大小

  • 跟服務端配合利用瀏覽器快取機制.啟用cdn、啟用gzip等

  • 減少網路協議過程中的消耗,減少http 請求、減少dns查詢、避免重定向

  • 最佳化關鍵渲染路徑,非同步載入js等

但是有的最佳化手段我們不容易排查,因為他是打在包裡面的,這個js 檔案包含了很多邏輯怎麼辦,這裡我有兩個手段或許能夠幫助排查出效能瓶頸發生在哪裡:

分析包內容

在通常情況下,我們無法判斷的最佳化點,都是在打包後,我們無法分析出,那些東西不是我們在首屏必須需要的,從而不能做出針對新的最佳化,為了解決當前問題,各大bundle廠商也都有各自的分析包的方案

以vue-cli 為例

| 1 | "report"``: "vue-cli-service build --report" |

我們只需要在腳手架中提供以上命令,就能在打包時生成,整個包的分析檔案

如上圖所示 在打包後就能分析出打包後的js 檔案他包含什麼元件,如此以來,我們就能知道那些檔案是沒必要同步載入的,或者走cdn的,透過配置將他單獨的隔離開來,從而找出效能的問題

利用chorme devtool 的程式碼覆蓋率

如下圖所示,

利用 devtool的程式碼覆蓋率檢查就能知道那些js 或者css 檔案的程式碼沒有被使用過,結合包內容的分析,我們就能大概的猜出效能的瓶頸在哪裡從而做相應的特殊處理

針對vue 的特殊最佳化


以上內容都是通用的一些最佳化手段,您在哪都能查到,只是我表達了一下做這些常規最佳化的深層原因。能讓您更清楚的瞭解這些原因之後,在效能瓶頸的時候能遊刃有餘,而不是為了面試死記硬背,一到用的時候就不靈

然後我司是vue啊,我們得上得vue 的手段

圖片懶載入

所謂圖片懶載入,就是頁面只渲染當前可視區域內的圖片,如此一來,減少了其他圖片渲染數量,能大大提高SpeedIndexLCP的時間,從而提高分數

在vue中提起圖片懶載入外掛,首推:vue-lazyload

使用方式簡單,功能豐富

虛擬滾動

在一含有長列表頁面中,你有沒有發現你是往下越滑越卡,此時虛擬滾動就排上用場了, 他的基本原理就是隻渲染可視區域內的幾條資料,但是模擬出正常滑動的效果,因為每次只渲染可是劇域內的資料,在滑動的時候他的效能就會有飛速提升

在vue中比較好用的外掛有兩個:

目前我司統一用的vue-virtual-scroll-list 他下拉的時候到了分頁的地方能加些loading提示

vue 中的函式式元件

在vue中我們知道元件的初始化是比較損耗效能的,大家可以去試一下,使用vue 直接渲染一個文字內容,和直接渲染一個app.vue 元件他的分數是略有不同的。

但是當有了函式式元件,這個問題就迎刃而解了

因為函式是元件顧名思義他就是個函式,說白了就是個render函式,他少了元件初始化的過程,省去了很多初始化過程的開銷

什麼時候用函式式元件呢?

當你的元件中沒有業務邏輯只展示內容時,這時候函式式元件就派上用場了

利用v-show 、KeepAlive 複用dom

我們知道v-show是透過display 控制dom的展示隱藏,他並不會刪除dom 而我們在切換v-show的時候其實是減少了diff的對比,而KeepAlive 則是直接複用dom,連diff 的過程都沒了,並且他們倆的合理使用還不會影響到初始化渲染。如此一來減少了js 的執行開銷,但是值得注意的是,他並不能最佳化你初始化的效能,而是操作中的效能

分批渲染元件

在前面我們提到過SpeedIndex 的漸進渲染是提高SpeedIndex的關鍵,有了這個前提,我們就可以分批非同步渲染元件。先看到內容,然後在渲染其他內容

舉個例子:

<template>
    <div>
        {{ data1 }}
    </div>
    <div v-if="data1">
        {{ data2 }}
    </div>
</template>
<script>
import { ref } from 'vue'
export default {
    setup() {
        let data1 = ref('')
        let data2 = ref('')
        // 假設 這是從後端取到的資料
        const data = {
            data1: '這是渲染內容1',
            data2: '這是渲染內容2'
        }

        data1.value = data.data1
        //利用requestAnimationFrame 在空閒的時候當前渲染之後在渲染剩餘內容
        requestIdleCallback(() => {
            data2.value = data.data2
        })
        return {
            data1,
            data2
        }
    },

}
</script>

上述例子比較簡單可能描述的不太貼切,在這裡特此說明一下,當前方法適用於元件內容較多,每次render 時間過長,導致白屏時間過長,比如,一次拉取使用者列表,那麼分批渲染就非常合適,先展示一部分使用者資訊,最後直到慢慢將所有內容渲染完畢。如此對瀏覽器的SpeedIndex 也非常友好

最後


效能最佳化一直是一個很火的話題, 不管從面試以及工作中都非常重要,有了這些最佳化的點,你在寫程式碼或者最佳化老專案時都能遊刃有餘,能提前考慮到其中的一些坑,並且規避。

但是大家需要明白的是,不要為了效能最佳化而效能最佳化,我們在要因地制宜,在不破壞專案可維護性的基礎上去最佳化,千萬不要你最佳化個專案效能是好了,但是大家都看不懂了,這就有點得不償失了,還是那句話,60分萬歲61份浪費,差不多得了,把經歷留著去幹更重要的事情!

本作品採用《CC 協議》,轉載必須註明作者和本文連結
最美的不是下雨天,而是和你一起躲過的屋簷!

相關文章