Web 頁面優化專項 > Lighthouse > 效能分數優化

linong發表於2022-03-11

Lighthouse 簡介

Lighthouse 是一個開源自動化工具,可以用於改進 Web 應用的質量。

Lighthouse 目前已經整合在新版本 Chrome DevTools 中,也可以將其作為一個 Chrome 擴充套件程式執行,或從命令列執行。

Lighthouse 會針對 Web 頁面執行一些自動化測試,生成一個有關頁面效能的報告,然後就可以根據測試報告來優化頁面

Lighthouse 測試報告包含五塊內容:效能、無障礙、最佳做法、SEO、PWA,我們今天主要是針對效能報告提出優化方案及復現程式碼

image.png

Chrome DevTools 可以調整語言喲,比如說從英文改成中文。

Lighthouse 效能指標

  1. First Contentful Paint
    FCP 指首次內容渲染時間,標識網頁首次渲染出首個文字、圖片(頁面上的影像、非白色 <canvas> 元素和 SVG 被視為 DOM 內容。不包括 iframe 內的任何內容)的時間。 FCP 分點陣圖
  2. Time to Interactive
    TTI 指可互動時間,標識網頁需要多長時間才能提供完整互動功能。我們知道 JS 主執行緒是單執行緒的,如果有長 JS 任務是會阻塞頁面互動
  3. Speed Index
    速度指數表明了網頁內容的可見填充速度
  4. Total Blocking Time
    TBT 指累計阻塞時間,標識網頁首次內容渲染 (FCP) 和可互動時間之間所有超時任務的超時累計時間,按 60FPS 算,時間應該是不超過 16ms
    超時任務指任務用時超過 50ms,如果 Lighthouse 檢測到一個 70ms 長的任務,則阻塞部分將為 20ms。
  5. Largest Contentful Paint
    LCP 指 最大內容繪製,標識網頁渲染出最大文字或圖片的時間。類似於 First Meaningful Paint(FMP 首次有效繪製)(主要內容對使用者可見的時間) 指標,但是 LCP 是一個通用固定計算規則
  6. Cumulative Layout Shift
    CLS 指累積佈局偏移,標識網頁可見元素在視口內的移動情況計算規則

    • 比如說在網上閱讀一篇文章,結果頁面上的某些內容突然發生改變?文字在毫無預警的情況下移位,導致您找不到先前閱讀的位置。
    • 比如正要點選一個連結或一個按鈕,但在手指落下的瞬間,誒?連結移位了,結果點到了別的東西!大多數情況下,這些體驗只是令人惱火,但在某些情況下,卻可能帶來真正的破壞(點拒絕結果變成了允許)。
    • 或者想想變態貓?看著是平平無奇,但是操作的時候飛來橫禍。

image.png

LCP、FCP 示例

圖片來源:https://web.dev/lcp/#-2

image.png
image.png
image.png
image.png

Lighthouse 優化建議

建議一:減少未使用的 JavaScript、CSS 程式碼

image.png

手段1:非同步。等需要時再載入

名詞有很多:懶載入、延時載入、閒時載入、按需載入等等。

  1. 「懶載入」import 非同步元件

    1. 比如說我們有一個訊息中心模組,內部有使用者端和管理端頁面。這兩個頁面應該使用非同步元件。因為目的不是發就是收,而且管理端是需要許可權才可以看到的。
    2. 比如說我們的訊息模組,支援 markdown、富文字、docs、xlsx 等等型別,但是一條訊息只能顯示一個型別,所以我們可以把 DocsView 來封裝一下,動態載入元件渲染。
  2. 「懶載入」import 非同步功能

    1. 比如說我們的表格有匯出功能,基於 xlsx 實現。這樣一個功能也不是高頻功能,所以我們也可以通過 import 來非同步載入使用。
  3. 「延時載入」通過手動調整優先順序、或者延時器來實現功能。
    需要注意 CLS 指標,這裡需要注意不要造成 CLS 指標異常。

    1. 還是我們訊息模組的例子,在 LCP 之前我們不去載入是否有最新訊息,等 LCP 之後再去載入。這裡對於首屏減少了請求,對於使用者的影響也會比較小 (只顯示一個紅點,使用者有紅點和無紅點的點選效果都是檢視訊息列表)。
    2. 對於關注、點贊、收藏等開關模組需要慎重。因為效果是相反的,本來是關注,結果有可能變成了取消。

手段2:搖樹優化 Tree-Shaking

可以將未使用的程式碼邏輯在編譯時刪除。
因為 Webpack 4 是預設開啟狀態,所以我們只說一些限制條件。

  • 只對 ES Module 起作用,對於 commonjs 無效,對於 umd 亦無效。
  • 需要包本身支援才可以。
  • 注意配置 sideEffects ,防止 Css 被優化
  • 需要 build 時才會優化。process.env.NODE_ENV === 'production' 狀態

手段3:babel 降低適配版本

設定合理的 .browserslistrc,進行 babel、babel-polyfill 轉義。
比如說只支援 Chrome 的後臺系統,就不需要轉義 IE 系列了,這樣可以大大減小體積。

總結&注意事項

  1. css 的優化,也可以依賴非同步元件。
  2. 三方庫可以考慮使用元件做二次封裝。
  3. 正確的區分 v-if 和 v-show,深入研究 el-tabs 實現機制。確保元件並沒有被真實的例項化
  4. 可以通過 chrome-devtools 中 coverage 來檢視哪些程式碼沒有被執行。
    可以分析具體有哪些程式碼沒有被執行到,我們期望的結果是載入的每一行程式碼都是有用的。
    可以看到有一個資源一大半程式碼都沒有被用到,這都是浪費。如果頻寬是按量付費的人得哭死。
    image.png
  5. last 2 verions 表示支援所有版本後兩位。也包括永遠不更新的 IE

建議二:優化體積、消除重複程式碼

手段1:壓縮

  1. 資源伺服器開始 Gzip 壓縮,設定資源 30D 快取。然後通過檔名 hash 來更新
  2. CDN 一般來說都會支援壓縮和快取,並且頻寬也是比較靠譜的,節點距離使用者也比較近。
  3. jsmin、搖樹優化等方案。
  4. 注意圖片型別。純色圖片 png,複雜圖片 jpg,webp 更小。
  5. 注意圖片尺寸。儘量不要過大,注意裁圖。

手段2:消除重複包。依賴共享

  1. lerna + yarn
    常見重複包(axios、ui 庫),因為我們好多元件都是基於業務封裝(什麼叫基於業務封裝?內部有邏輯,存在資料呼叫自動更新,UI 可以直接在業務內使用)。
    我們使用了 lerna 來做相同版本共享,我們一般要求使用相同版本
  2. peerDependencies
    因為某些原因,有一些包並不是所有專案都有的,或者說對於版本有強依賴。我們也需要配置 peerDependencies讓使用方可以安裝正確版本的依賴
  3. externals
    部分三方庫版本號是 0.xx.x,因為 lerna 是基於首位不為 0 的進行比較是否為相同版本。導致 axios@0.23axios@0.24 不認為是相同版本。
    這個時候我們會使用 externals 來強制不打包,讓使用方來提供。
  4. 統一構建工具及版本
    因為有時候我們會有一些基礎包(babelbabel-polyfill),但是各個 cli 版本使用的版本、方式不一樣,對於轉義處理也是不一致的,為了使用最小的包體積,我們要求相同相同版本的 cli webpack

手段3:替換包,選擇更小的版本

  1. 小包替換大包,按需包替換全量包

    1. momentjs 改為使用 dayjsmomentjs 包只使用 format 大概在 100k 左右,而 dayjs 只有 10k 不到。
      這是因為 momentjs 裡面打入了 i18n 語言包。
      所以還有另一個方案就是改成語言按需引入。
    2. 避免 import _ from 'lodash ,而是使用 lodash-es。

建議三:減少重排、減少佈局變動、減少 DOM 數量

可以理解為 CLS 指標。

手段1:使用固定寬高

image.png

一般來說都有固定高度(24*24),我們把它限制在一個範圍內。防止因為圖片非同步載入回來,撐開 dom 後,導致其後資料全部發生變動。

  1. 比如說在文章類頁面中,如果有記住上次瀏覽位置。如何保證使用者還能定位到上次位置?如果不使用固定寬高,那麼使用者有可能看到的內容會一直發現變化。
  2. 比如說在微信聊天頁面,如何一直定位在頁面最底部?當圖片載入完成之後會出現高度變化。

手段2:虛擬化、虛擬列表、墓碑機制、分頁載入、懶載入

虛擬列表類似實現一個最差邊界方案,我只顯示 20 個,這就是我效能最差的時候。不管 100 個、1000 個,我只顯示 20 個。

  1. select、table、tree 等長列表注意使用虛擬列表。

    1. 比如說微信聊天,左邊的會話列表大家會刪除嘛?估計會有幾百、幾千個,包括單聊、群聊、通知、公眾號等等。
      會有幾個節點?頭像、名稱、訊息摘要、時間、遮蔽、未讀等等,就算會有 10 個標籤。10 * 1000 這樣就一萬個標籤了,如果使用虛擬列表,10 * 50 這樣也才 500 個標籤。
    2. 比如說有一些樹節點,層級覆蓋下去有可能會在幾十萬個節點,如果再操作選中之類的邏輯
  2. 異常的 tooltip 節點。

    1. 比如說會提前渲染元件。
    2. 比如說會進行頻繁變更。

建議四:降低記憶體佔用、降低 CPU 使用率

手段1:圖片懶載入

圖片會佔用實際記憶體,導致卡頓。

  1. 注意圖片尺寸。儘量不要過大,注意裁圖。
  2. 只載入視口的圖片。其他圖片按需載入,參考文章頁面返回上次閱讀邏輯功能,如果不按需會導致無法檢視圖片。

手段2:減少 JS 程式碼、減少 CSS 程式碼

參考上面的邏輯就好了。

程式碼減少,下載、解析、執行的時候當然都會減少呀

建議您減少為解析、編譯和執行 JS 而花費的時間。您可能會發現,提供較小的 JS 負載有助於實現此目標。

image.png

建議五:降低網路負載、加快使用者下載速度

資源下載速度限制條件一般有什麼?

  1. 頻寬(吞吐)1M小水管
  2. 距離(遠近)華北地方內訪問、全球訪問
  3. 介質(穩定)有線、無線、WiFi、5G、4G

手段1:增加頻寬

需要充錢才能解決的問題都不予解決?

如果是伺服器頻寬不夠,那麼解決辦法就是充錢。
如果是使用者頻寬不夠,那麼只能好好優化。

手段2:上 CDN

CDN 的關鍵技術主要有內容儲存和分發技術,使使用者就近獲取所需內容,降低網路擁塞,提高使用者訪問響應速度和命中率。

簡單來說就是距離使用者近,頻寬大,網路暢通不擁塞。

手段3:縮小體積

(同建議一、建議二)

手段4:增加快取

image.png

前端專案一般 html 不快取,然後資源通過 hash name 來更新。

世界級別的難題:如何讓快取過期如何讓快取不過期

建議六:縮短執行時間、縮短執行鏈路、避免阻塞主執行緒

手段1:減少主執行緒工作,優化程式碼執行速度

  1. 正確使用迴圈。應該先執行 filter,再執行 map
    因為 filter 之後,數量會變少(隨機數,變成一半),兩個示例等於 1.5倍 和 2倍 的對比。
    那麼有什麼辦法可以 1 倍出結果嗎?(reduce?)
    image.png
  2. 正確使用迴圈。如果不使用結果,應該使用 forEach 遍歷。
    image.png
  3. 優化巢狀迴圈。上面的例子充其量就是 N * 2,巢狀迴圈就會變成 N²。一般出現在樹狀結構,比如說許可權
    例子採用去重來說明差距
    image.png
  4. 使用 lodash 中的方法來簡化資料處理邏輯。
    可以實現程式碼少、效率高、語義明確。

    Object.entries(treeData).filter(([, v]) => v.level === 0).forEach(([key]) => { 這行程式碼想做什麼?有什麼效能上的問題嗎?
    image.png
    Object.entries(treeData) 的效能偏差,tree 節點量級是多少?考慮用 lodash 提供的方法,效能會好一點(迴圈次數變少,3次降成1次)、程式碼變得更少了(減少了無用的語義)、語義也更加明確了(把物件轉換成keyvalue陣列,過濾掉有level的,遍歷執行邏輯 => 遍歷物件)。

手段2:減少序列程式碼,縮短請求鏈路

  1. await、async 的亂用
    image.png

建議七:避免過時的程式碼

Vue、react 之類框架程式碼還好,有統一的管理方式。常見的是一些老專案、js、jQuery 專案。

  1. 禁止 document.write
  2. 樣式放上面、JS 放下面。不要阻塞 DOM 解析。

總結

優化之路,相輔相成。

  1. 優化載入速度。擴大頻寬、快取、內容分發、減小體積。
  2. 優化體積。減少損耗,減少解析時間。
  3. 優化邏輯。懶載入、非同步載入、減少無效損耗、提升執行效率。

專案實戰

我提供了一些最簡案例在倉庫中:Demo 倉庫地址,你只需要看例子就可以直接看到問題(然後你可以去優化它)。

相關文章