前端業務開發的通用經驗 - 性能優化

性能優化體系四要素:指標、監控、性能分析、優化方案。javascript

指標

什麼是性能?前端業務開發關注的性能,主要有兩個:加載速度、渲染效率。兩者通常也合稱【性能體驗】。css

加載速度

衡量加載速度的傳統指標通常是:首字節、DOMContentLoaded、onLoad。傳統指標的問題是,徹底站在技術視角衡量,並不能表明用戶實際的體驗。目前受業界普遍承認的是谷歌提出的一套面向用戶的指標 Progressive Web Metricshtml

  • FP(First Paint)第一個像素繪製出來了
  • FCP(First Contentful Paint)白屏結束,有可見元素出現了
  • FMP(First Meaningful Paint)對用戶有意義的內容出現了
  • TTI(Time to Interactive)沒有長任務阻塞,能夠交互了(圖中 First Idle 位置)

通常用 FCP 定義【白屏時間】,用 FMP 定義【首屏時間】。FP 和 FCP 可直接經過瀏覽器 api 得到,但 FMP 顯然沒法由瀏覽器來定義。通用實現的一種基本思路是檢測 DOM 節點變化最大的時刻,固然最精確的仍是得靠自定義埋點,畢竟這個指標歸根結底是用於幫助開發掌握頁面在線上運行的實際情況,也只有開發最清楚頁面內容的加載過程。TTI 最好也用自定義埋點,靠技術手段檢測會比較麻煩前端

不過真從用戶體驗角度看,FP 和 FCP 意義也不是很大,而 FMP 又難以標準化定義,考慮到這些問題,谷歌又提出了三個新的指標:java

  • Largest Contentful Paint (LCP):頁面最大的元素出現了(算法
  • Total Blocking Time (TBT):FCP 到 TTI 之間長任務的時長總和
  • Cumulative Layout Shift (CLS):衡量可視區元素向下跳動的程度(一般是由於上方元素突現或高度變化致使頁面重繪)

這三個指標連同其餘通用指標,歸結成了一個新項目:web vitals。具體介紹可參考這篇文章:The New Generation of Performance Metrics for Better User Experiencereact

在實踐中,咱們須要在關鍵路徑上的各個節點添加監控埋點,從 url 發出請求,到 DNS 解析,到服務端處理(若是是服務端渲染的話),到 html 開始加載,到阻塞類資源加載,到業務代碼開始執行,到接口返回,到內容正式渲染。這樣纔有利於分析到底是哪一個環節影響了最終性能。git

張克軍的這張圖描繪了網頁通用的加載模型,在實際業務中一般還存在其餘一些環節。好比移動端 hybrid 頁面或 RN 頁面的容器初始化過程,好比前置校驗用戶登陸態、獲取用戶定位等等。github

上述指標只是衡量了單個頁面的性能,看不到產品總體的性能,所以大廠基本都在搞一個叫【秒開率】的指標。秒開率是指整個樣本集中,某指標小於 1s 的佔比。秒開率回答了這樣一個問題:整個產品有多少頁面的性能指標(通常取 90 分位線)小於 1s。web


渲染效率

渲染效率就是指頁面流暢度,看交互動效是否有卡頓(掉幀)。衡量渲染效率的指標主要是 FPS。查看 FPS 最簡單的辦法有兩個,一是用 stats.js,二是在 chrome devtools 中經過 command + shift + p 調出命令窗口,輸入 fps 便可調出幀率面板。ajax

因爲 UI 線程和 js 線程共用同一個線程,js 任務的運行可能致使 UI 卡頓、卡死,所以瀏覽器還提供了一個用於監控 long task 的 api。

參考:百度APP流暢度全流程質量監控實踐(一) 流暢度現狀分析


監控

一般咱們發現某個頁面性能已經不好了,因而一頓專項優化,最後各項指標都達到了預設目標,但問題是怎麼保證性能長期不會繼續劣化呢?爲了知道頁面在線上運行的實際性能情況,首先就得把監控體系創建起來,能對採集到的指標數據作各類聚合展現以便隨時看到頁面指標數據變化,能按期自動生成報表,能對指標異常變化(好比突增、驟降)添加報警。

爲了採集性能數據,瀏覽器提供了一系列專門用於監控性能的 API,例如 navigation-timingresource-timinguser-timing 等。通用指標可封裝 sdk 進行採集上報,sdk 應該同時提供上報自定義測速點的能力。

除了線上監控,性能保障還有賴於事前防控。好比在上線流程,應該有一道性能檢測環節(checklist),防止一些明顯的、常見的、比較極端的性能劣化場景,好比上了一個特別大的資源而不自知(不當心全量引入了 lodash、加了張沒壓縮的大圖、引入了無用的資源等)。最簡單的思路,就是上傳到 ligthouse,若是檢測得分低於閾值,就直接禁止上線。這種檢測既能夠在上線前執行,也能夠按期循環執行。


性能分析

一般監控已經能提供比較詳細的性能情況數據,不過排查問題時一般還須要一些工具輔助以得到更加細節的信息。

瀏覽器提供了一系列衡量性能的工具,可查看資源加載時序分析網頁性能分析代碼執行耗時分析渲染流暢度等。

現代框架通常也提供了運行時的性能分析工具。好比 react profiler,可方便的看到組件級乃至方法級的執行耗時(針對【渲染效率】)。

其餘分析工具


加載性能優化

性能優化的基本思路,是搞清楚整個加載鏈路出問題的環節,而後針對性的修復。具體的手段,主要有四種類型:

  • 體量規劃
  • 時序規劃
  • 距離規劃
  • 定向優化

其實還有一種嚴格說只能算體驗優化的手段,就是從交互上讓用戶感受得快,好比骨架屏,還有圖片的漸進式加載(可參考 how medium does progressive image loading)。


體量規劃

一般來講,加載資源的總量越小,加載性能越好。針對 web 而言,主要是限制請求資源的體積。單從資源體積的角度看,理想狀況是徹底的按需加載,即每時每刻僅加載當前須要的資源。

  • 只請求當前環境須要的資源
  • 減少資源體積(雅虎14條之四、10)
    • uglify
    • Gzip(服務端開啓,只壓縮文本文件,不要壓縮圖片這類的二進制文件,緣由
    • 圖片優化(壓縮、像移動端同樣針對屏幕尺寸和分辨率加載不一樣大小/質量的圖片、根據網絡情況加載不一樣質量的圖片、用 webp 格式)
    • 控制 cookie 大小
    • 控制 header 大小
    • 用字符數最少的代碼:例如用 void 0 代替 undefined
  • 減少無效資源(雅虎14條之四、12)

時序規劃

小學奧數裏有個主題叫統籌規劃,其中有一類問題就是看如何安排各類事情的執行流,以最小化總時間。時序規劃也是相似,最基本的兩種思路,就是併發和前置,要麼一塊兒搞,要麼提早搞。

  • 並行
  • 前置
    • SSR:將模板渲染前置到服務端處理
    • prefetch、prerender、preload
    • 容器預初始化
    • 管道:無需等全部數據加載完才處理,而是加載一部分就處理一部分(html 的渲染就是這樣的)

距離規劃

距離規劃的基本原理是:傳輸速度有上限,所以距離越近,時間越短。距離最近的是寄存器/內存,最遠的是服務器。

  • CDN (雅虎14條之2)
  • 緩存
    • http 緩存(雅虎14條之三、13)
    • 使用可緩存的 get 請求(雅虎14條之14)
    • 服務器緩存
    • localStorage
    • web worker
  • kv-storage
  • stale-while-revalidate
  • 避免重定向(雅虎14條之11)
  • 服務端推送(單向傳輸,避免一來一回)

一般可以被緩存而且緩存能起到較大做用的,是不常變化、會被反覆用到的靜態信息。常常變化的信息,或者不多重複使用的信息,會影響緩存的命中率。要利用緩存,在設計上就須要考慮動靜分離,好比與用戶狀態無關的配置信息和與用戶狀態相關的動態信息分開使用不一樣接口。

爲了讓常常變化的數據也能使用緩存來提升效率,也有一種思路:每次都先從緩存裏取數據,而後每次都發送請求更新緩存,以時間換空間,以 1 次延遲的代價,來提升接口請求的性能。這種策略還有個專門的規範叫 stale-while-revalidate,基於 react hook 實現的網絡請求方法庫 swr 已經內置了該策略。


定向優化

前三種思路屬於通用思路,好比 CPU 的性能優化也會採用這些思路。而定向優化是指針對環境的特徵,提供專門的優化方案。就 Web 而言,特定環境主要是指:瀏覽器的請求、加載和渲染機制;http 協議;js 引擎。

針對瀏覽器機制


針對 http 1.1

  • 減小請求次數(雅虎14條之1)
    • 合併資源(bundle / spites)或合併資源的請求(CDN Combo)
    • 合併多個 ajax 請求
    • CSS inline
    • 使用 CSS、SVG、Inline Image、Icon-font 代替圖片
    • 避免使用 @import 和 iframes
    • 控制域名數量,減小 DNS 查詢(雅虎14條之9)
  • 選擇更先進的協議:UDP、QUIC、SPDY、http 2
  • 《Web 性能權威指南》

針對 js 引擎

  • 使用更高效的 api
    • jsperf.com/
    • 編寫有利於引擎優化的代碼。好比按照 asm.js 規範編寫的代碼,將直接得到引擎層面的優化支持
    • 《High Performance Javascript》

優化手段有不少,但收益並不相同,像針對 js 引擎的優化,基本只有框架會去作。有明顯收益的,主要是緩存、按需加載、減少資源體積、請求併發/前置,可參考淘寶天貓首頁性能對賭優化回憶錄(連接: pan.baidu.com/s/1mgILtDfD… 提取碼: impp),能夠說是把這些手段用到了極致。


渲染效率優化

優化渲染效率,提升交互反饋及時性和動畫流暢度,也能夠運用和加載性能優化同樣的思路。提升動畫幀率的一個關鍵點,是處理動畫渲染和 js 的關係,避免在動畫期間執行 js 長任務,畢竟 60FPS 意味着每一幀只有 16ms,若是這 16ms 全用來執行 js 了,那麼動畫必然會因掉幀而出現卡頓。

  • 體量規劃
    • 按需繪製,只渲染須要的元素
    • 函數節流,下降 js 執行頻率
    • 優雅降級,例如針對低端設備,動效元素少用陰影和漸變色(純色、扁平化)
  • 時序規劃
    • 提早渲染好組件,再執行動畫
  • 距離規劃
    • 離線更新(例如使用 Document Fragment、離屏 canvas)
  • 瀏覽器渲染層面
    • 控制 DOM 和 CSS 的層級深度
    • 合併多個 DOM 操做
    • 少用 js 動畫,如必要,使用專門的動畫 api:requestAnimationFrame
    • 使用 requestIdleCallback 執行 js 任務
    • 硬件加速(hardware-acceleration
    • 減小重排
  • 其餘參考
相關文章
相關標籤/搜索