很多人在剛開始學習做性能優(yōu)化的時候,就是眉毛胡子一把抓,把知道的、想做的、能做的都一股腦都做了,這就是典型的看上去很勤奮的奮斗逼!
苦哈哈的干了很多活,也落了地,確實性能得到了優(yōu)化。但是要知道代碼是一坨一坨的長大的,它不是你在那里優(yōu)化一下,它就不動了的,于是乎過不了多長時間,又是一番難以想象的光景!
真正懂得優(yōu)化的高手是真正懂得“權衡”真諦哲學的人。其實我們說一個人在權衡的時候,它的底層邏輯其實就是他的認知范圍內的數(shù)據(jù)的比較,是2/8原則,是優(yōu)先級,是四象限法則,是時間管理,是人力管理,是成本管理,是抓大放小,更是審時度勢!
環(huán)境判定
首先要了解自己是處于一個什么樣的環(huán)境。是做移動端頁面的優(yōu)化還是 PC 端頁面的優(yōu)化,是做首屏頁面的優(yōu)化還是做次級頁面的優(yōu)化,是做首跳場景的優(yōu)化還是做二跳場景的優(yōu)化...
數(shù)據(jù) KPI
光是知道干活不知道拿錢的,一般我們稱之為傻瓜。所以一開始你就要明確的知道什么東西是可以幫助你賺 money 的。
如果你有關注客戶服務和銷售團隊的常見工單,研究高跳出率和低轉化率的原因的話,會發(fā)現(xiàn)也許你覺得非常重要的某些指標其實并那么重要(比如一個季度都沒什么人用的功能頁面)。
我想現(xiàn)在你知道我在說的是業(yè)務價值,一定要把你的每一分錢都花在刀刃上!
假設你想比你的競爭對手至少快20%,這個時候你需要去選定你的目標。
首字節(jié)時間,是反映服務端響應速度的重要指標 瀏覽器開始收到郁南服務器響應數(shù)據(jù)的時間 = 后臺處理時間 + 重定向時間 從用戶首次與網(wǎng)站進行交互到瀏覽器實際上能夠響應該交互的時間 可以集成web-vitals 用戶從打開頁面開始到頁面開始有東西呈現(xiàn)為止 (chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime)*1000 window.performance.timing.responseEnd – window.performance.timing.fetchStart TTFB(Time To First Byte): FID(First Input Delay): 白屏時間(first Paint Time) 核心內容展示時間(Largest Contentful Paint) 資源大小
這些是不是你覺得最應該關注的??墒沁@還不夠,因為你是迷茫的。如果變成這樣:
3G環(huán)境下
FID < 100ms:從用戶首次與網(wǎng)站進行交互到瀏覽器實際上能夠響應該交互的時間 TTI < 5s:布局已經(jīng)穩(wěn)定、關鍵的Web字體可見、并且主線程已經(jīng)空閑下來可以處理用戶輸入的時間點 LCP < 3s:標記可視區(qū)已加載頁面重要內容的時間點 關鍵文件大小 < 170KB (gzip壓縮后)
是不是覺得可以開干了!來思考一個問題,你的頁面現(xiàn)在的問題到底在哪?是什么東西影響了這些指標,是否有可衡量的工具?
性能監(jiān)測
拿到一個有問題的項目的時候,可以從以下幾個角度初探問題所在:
有沒有沒有用的接口仍然在調用;
有沒有加入非常大的第三方包沒有走CDN云加速;
有沒有并發(fā)請求阻塞資源的加載,比如一個頁面調了十幾個接口;
有沒有非常大的業(yè)務資源因輸入沒有控制,導致有超常規(guī)大小的業(yè)務資源;
這些問題是我在大型項目開發(fā)中遇到過的,因為它有一個非常響當當?shù)拿纸小皻v史遺留問題”,所以很容易成為釘子戶。
瀏覽器是有自帶一個 Performance 面板,具備可視化功能。但是你要知道什么人更加看重這個數(shù)據(jù),同時這些數(shù)據(jù)也不利于分享,最最重要的是它無法自動的隨時隨地、不間斷的進行監(jiān)視、測量。所以通常都會有自研的 SDK 去獲取 window.performance 下的數(shù)據(jù)進行二次處理后做更加深層次的可視化。有用的可視化數(shù)據(jù)會推動建立長期關注性能的團隊文化。
性能優(yōu)化的底層邏輯
其實深入思考一下就知道,性能優(yōu)化的環(huán)節(jié)無非網(wǎng)絡加載、渲染優(yōu)化、文件優(yōu)化、用戶體驗層面。其實什么 DNS、CDN、TCP、各級緩存、Gzip壓縮、代碼壓縮混淆啊什么的基本上在架構層都考慮到了,一勞永逸的事情,幾乎不需要你操心!用戶體驗上加個loading、進度條、骨架屏什么的也都是通用解決方案。
你總在思考解決方案的無非這三種情況:
在整個鏈路中減少中間環(huán)節(jié):比如將串行改成并行。 盡可能的預加載、預執(zhí)行、懶加載 漸進式、分片段 SSR 值不值得
SSR 是當用戶第一次請求頁面時,由郁南服務器把需要的組件或頁面渲染成 HTML 字符串,然后把它返回給客戶端。
通常人們考慮 SSR 方案時都是奔著解決 CSR 下的 SEO 問題以及首屏加載速度過慢的問題。這里我們主要考慮性能。大家思考一下直出方案到底優(yōu)化的是哪部分的時間?省去的是 **前端渲染 **以及 **ajax請求 **的時間吧!因為它把所有的計算都放到服務端了。
但是 html 會比 CSR 的文件大吧?。。⊥瑫r距離服務端遠的用戶也是會有比較長時間的白屏的!
離線包還是 PWA
所以通常移動端又會通過離線包技術去解決 html 本身文件加載時間的問題。離線包的基本思路就是通過 web view 統(tǒng)一攔截 URL,將資源映射到本地離線包,更新的時候對版本資源檢測、下載和維護本地緩存目錄中的資源。比如騰訊的 webso 和 Alloykit 的離線包方案。離線包是對 web 端而言相對透明、侵入性非常小的方案。
PWA 是通過純 web 的方案去加速和優(yōu)化加載性能。其通過 cacheStorage 緩存靜態(tài)資源。
但在傳統(tǒng)的 http cache 方案下,我們一般不會緩存 HTML。這是因為 CSR 的 html 是一個空殼,我們一般會設置比較大的 max-age,這樣在瀏覽器緩存過期時間內,用戶看到的永遠將是舊的頁面。
而對于直出 HTML,配合PWA,將從后臺直出的 html 文件緩存到 cacheStorage 中,在下一次請求時,優(yōu)先從本地緩存中獲取,同時發(fā)起網(wǎng)絡請求更新本地 html 文件。
接著又會發(fā)現(xiàn)有新的問題,就是第一次啟動時加載 html 資源還是費時間的。可以通過 app 端上支持預加載一個JS 腳本,拉取需要 PWA 緩存的頁面,提前完成緩存!
對于前端而言,PWA 無疑會是更好的解決方案,但是 PWA 不是萬能的,它有兼容性問題。比如只支持https。
另離線包方案和 PWA 方案本身是可以有一個數(shù)據(jù) PK 的。其實很多同學都是上了PWA,但從來沒想過如果嘗試把它拿掉,數(shù)據(jù)是否會有變化。
SSR 的成本
現(xiàn)階段我們知道的解決方案都是基于 Node 的,也就意味著要上 SSR 必須得有 Node 中間層。也即意味著你需要有能夠 hold 住 node 運維及架構能力的人力,還有服務端等硬件成本。同時因為服務端渲染的差異性,也會出現(xiàn)客戶端正常而服務端異常的情況。前后端分離可能會出現(xiàn)平滑發(fā)布的問題:當頁面的靜態(tài)資源(js、css)的發(fā)布不是與后端一起發(fā)布時,可能引起后端返回的 HTML 內容與前端的 JS、CSS內容不匹配的問題。如果沒做兼容處理,可能會出現(xiàn)樣式錯亂或者 document 選擇器找不到元素的問題。
所以加上SSR到底值不值得,小馬過河!通常我們建議首屏渲染體驗和 SEO 的優(yōu)化方案有很多,不到萬不得已不要用 SSR。
NSR
如果說 SSR 放到郁南服務器端成本比較大,那有沒有可能放到客戶端來。借助瀏覽器啟用一個JS-Runtime,提前將下載好的 html 模板及預取的 feed 流數(shù)據(jù)進行渲染,然后將 HTML 設置到內存級別的 MemoryCache 中,從而達到點開即看的效果。
這是一種將后臺請求壓力分發(fā)到各個客戶端中的方案,同時因為客戶端有數(shù)據(jù)預取和預加載,速度也能達到秒開。但是又有一個問題,預加載意味著你得是個算命先生!
ESI (Edge Side Include)
更重要的是如果是首跳頁面,什么預加載,預執(zhí)行,預渲染都旁邊呆著去吧!除了服務端和客戶端,我們是不是還有一個地方可以放資源,對了,就是代理端,比如 CDN。
CDN 比服務端距離用戶更近,有更短的網(wǎng)絡延時。在CDN云加速節(jié)點上將可緩存的頁面靜態(tài)部分先快速返回給用戶,同時在CDN節(jié)點上發(fā)起動態(tài)部分內容請求,并將動態(tài)內容在靜態(tài)部分的響應流后,繼續(xù)返回給用戶。
首屏 TTFB 會很短,靜態(tài)內容(例如頁面 Header 、基本結構、骨骼圖)可以很快看到。 動態(tài)內容是由 CDN 發(fā)起,相比于傳統(tǒng)瀏覽器渲染,發(fā)起時間更早,且不依賴瀏覽器上下載和執(zhí)行 js。理論上,最終 reponse 完結時間,與直接訪問服務器獲取完整動態(tài)頁面時間一致。 在靜態(tài)內容返回后,已經(jīng)可以開始部分 html 的解析,以及 js, css 的下載和執(zhí)行。把一些阻塞頁面的操作提前進行,等完整動態(tài)內容流式返回后,可以更快地展示動態(tài)內容。 邊緣節(jié)點與服務端之間的網(wǎng)絡,相比于客戶端與服務端之間的網(wǎng)絡,更有優(yōu)化空間。例如通過動態(tài)加速,以及 edge 與 server 之間的連接復用,能為動態(tài)請求減少 TCP 建連和網(wǎng)絡傳輸開銷。以做到最終動態(tài)內容的返回時間,比 client 直接訪問 server 更快。
ESR 是需要借助 CDN 的邊緣計算能力(保證了可以在CDN云加速上做類似于service worker的操作,可對請求和響應做靈活的編程),那如果 CDN 服務商不支持那就免談啦!
用到什么加載什么
懶加載
一次不加載完所有的文件內容,提前做拆分只加載此刻需要用到的那部分 當需要更多內容時,再對用到的內容進行即時加載
路由級別:
require.ensure(dependencies, callback, chunkName) Bundle-Loader
module 級別:
import()
內容級別:
圖片 lazy-load:
漸進式
把你的包拆的小小的,一點一點的加載。把最不重要的放到最后加載。
配合使用 webpack 的 code spiltting 配置、splitChunks、external、dll等。
沒用的代碼刪掉 模塊級別的冗余代碼:Tree-Shaking 碎片化的冗余代碼(如 console 語句、注釋等): optimization.minimize && optimization.minimizer 刪除多余的聲明語句:scope hoisting 代碼到底該怎么寫 先切換display:none再修改樣式 通過class的切換批量修改樣式 用transform屬性去操作動畫 meta viewport (可以加速頁面渲染) <img>標簽的 loading屬性 合理使用Canvas代替多DOM Tree 使用requestAnimationFrame 使用shouldComponentUpdate 及時清理定時器 使用 requestIdleCallback 使用PureComponent 使用 immutable-js 使用Fragment標簽 使用Element.getBoundingClientRect()獲取可視區(qū)域 使用VDOM 使用防抖和節(jié)流 大文件切片上傳 算法 總結
我從宏觀層面的監(jiān)測及數(shù)據(jù) KPI 到多端合作,最后從前端工程化以及微觀代碼層面講述了性能優(yōu)化的閉環(huán),另外的網(wǎng)絡狀況、CDN,ISP,緩存覆蓋,代理,第三方腳本、解析器阻塞模式、磁盤I/O、IPC延遲、防病毒軟件和防火墻,負載均衡、后臺CPU任務、和服務器配置等對web性能有著明顯影響但是前端同學平常無法或較少關注的層面、以及靜態(tài)資源例如圖片、字體等的處理等大家較為熟悉的層面,因為考慮到網(wǎng)絡上這方面的文章較多未多贅述。
學過算法的同學應該都知道,加速本質上就是在用更多的網(wǎng)絡、內存和 CPU 換取速度,以空間換時間!
你要知道做性能優(yōu)化在某些場景下你的決策是正確的,在某些場景下你的決策就是錯誤的。這才叫權衡!自動的隨時隨地、不間斷的進行監(jiān)視、測量你的項目性能,這是性能優(yōu)化成熟的標志,因為你的環(huán)境和需求是一直在變化的。
最后重點推薦一個性能優(yōu)化的案例學習網(wǎng)站 WPO Stats,這個網(wǎng)站上有很多很不錯的性能優(yōu)化的案例分享。