很多人在剛開始學習做性能優(yōu)化的時候,就是眉毛胡子一把抓,把知道的、想做的、能做的都一股腦都做了,這就是典型的看上去很勤奮的奮斗逼!
苦哈哈的干了很多活,也落了地,確實性能得到了優(yōu)化。但是要知道代碼是一坨一坨的長大的,它不是你在那里優(yōu)化一下,它就不動了的,于是乎過不了多長時間,又是一番難以想象的光景!
真正懂得優(yōu)化的高手是真正懂得“權衡”真諦哲學的人。其實我們說一個人在權衡的時候,它的底層邏輯其實就是他的認知范圍內(nèi)的數(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é)時間,是反映服務端響應速度的重要指標 瀏覽器開始收到鎮(zhèn)寧服務器響應數(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) 核心內(nèi)容展示時間(Largest Contentful Paint) 資源大小
這些是不是你覺得最應該關注的??墒沁@還不夠,因為你是迷茫的。如果變成這樣:
3G環(huán)境下
FID < 100ms:從用戶首次與網(wǎng)站進行交互到瀏覽器實際上能夠響應該交互的時間 TTI < 5s:布局已經(jīng)穩(wěn)定、關鍵的Web字體可見、并且主線程已經(jīng)空閑下來可以處理用戶輸入的時間點 LCP < 3s:標記可視區(qū)已加載頁面重要內(nèi)容的時間點 關鍵文件大小 < 170KB (gzip壓縮后)
是不是覺得可以開干了!來思考一個問題,你的頁面現(xiàn)在的問題到底在哪?是什么東西影響了這些指標,是否有可衡量的工具?
性能監(jiān)測
拿到一個有問題的項目的時候,可以從以下幾個角度初探問題所在:
有沒有沒有用的接口仍然在調(diào)用;
有沒有加入非常大的第三方包沒有走CDN云加速;
有沒有并發(fā)請求阻塞資源的加載,比如一個頁面調(diào)了十幾個接口;
有沒有非常大的業(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 是當用戶第一次請求頁面時,由鎮(zhèn)寧服務器把需要的組件或頁面渲染成 HTML 字符串,然后把它返回給客戶端。
通常人們考慮 SSR 方案時都是奔著解決 CSR 下的 SEO 問題以及首屏加載速度過慢的問題。這里我們主要考慮性能。大家思考一下直出方案到底優(yōu)化的是哪部分的時間?省去的是 **前端渲染 **以及 **ajax請求 **的時間吧!因為它把所有的計算都放到服務端了。
但是 html 會比 CSR 的文件大吧?。?!同時距離服務端遠的用戶也是會有比較長時間的白屏的!
離線包還是 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,這樣在瀏覽器緩存過期時間內(nèi),用戶看到的永遠將是舊的頁面。
而對于直出 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 內(nèi)容與前端的 JS、CSS內(nèi)容不匹配的問題。如果沒做兼容處理,可能會出現(xiàn)樣式錯亂或者 document 選擇器找不到元素的問題。
所以加上SSR到底值不值得,小馬過河!通常我們建議首屏渲染體驗和 SEO 的優(yōu)化方案有很多,不到萬不得已不要用 SSR。
NSR
如果說 SSR 放到鎮(zhèn)寧服務器端成本比較大,那有沒有可能放到客戶端來。借助瀏覽器啟用一個JS-Runtime,提前將下載好的 html 模板及預取的 feed 流數(shù)據(jù)進行渲染,然后將 HTML 設置到內(nèi)存級別的 MemoryCache 中,從而達到點開即看的效果。
這是一種將后臺請求壓力分發(fā)到各個客戶端中的方案,同時因為客戶端有數(shù)據(jù)預取和預加載,速度也能達到秒開。但是又有一個問題,預加載意味著你得是個算命先生!
ESI (Edge Side Include)
更重要的是如果是首跳頁面,什么預加載,預執(zhí)行,預渲染都旁邊呆著去吧!除了服務端和客戶端,我們是不是還有一個地方可以放資源,對了,就是代理端,比如 CDN。
CDN 比服務端距離用戶更近,有更短的網(wǎng)絡延時。在CDN云加速節(jié)點上將可緩存的頁面靜態(tài)部分先快速返回給用戶,同時在CDN節(jié)點上發(fā)起動態(tài)部分內(nèi)容請求,并將動態(tài)內(nèi)容在靜態(tài)部分的響應流后,繼續(xù)返回給用戶。
首屏 TTFB 會很短,靜態(tài)內(nèi)容(例如頁面 Header 、基本結構、骨骼圖)可以很快看到。 動態(tài)內(nèi)容是由 CDN 發(fā)起,相比于傳統(tǒng)瀏覽器渲染,發(fā)起時間更早,且不依賴瀏覽器上下載和執(zhí)行 js。理論上,最終 reponse 完結時間,與直接訪問服務器獲取完整動態(tài)頁面時間一致。 在靜態(tài)內(nèi)容返回后,已經(jīng)可以開始部分 html 的解析,以及 js, css 的下載和執(zhí)行。把一些阻塞頁面的操作提前進行,等完整動態(tài)內(nèi)容流式返回后,可以更快地展示動態(tài)內(nèi)容。 邊緣節(jié)點與服務端之間的網(wǎng)絡,相比于客戶端與服務端之間的網(wǎng)絡,更有優(yōu)化空間。例如通過動態(tài)加速,以及 edge 與 server 之間的連接復用,能為動態(tài)請求減少 TCP 建連和網(wǎng)絡傳輸開銷。以做到最終動態(tài)內(nèi)容的返回時間,比 client 直接訪問 server 更快。
ESR 是需要借助 CDN 的邊緣計算能力(保證了可以在CDN云加速上做類似于service worker的操作,可對請求和響應做靈活的編程),那如果 CDN 服務商不支持那就免談啦!
用到什么加載什么
懶加載
一次不加載完所有的文件內(nèi)容,提前做拆分只加載此刻需要用到的那部分 當需要更多內(nèi)容時,再對用到的內(nèi)容進行即時加載
路由級別:
require.ensure(dependencies, callback, chunkName) Bundle-Loader
module 級別:
import()
內(nèi)容級別:
圖片 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)絡、內(nèi)存和 CPU 換取速度,以空間換時間!
你要知道做性能優(yōu)化在某些場景下你的決策是正確的,在某些場景下你的決策就是錯誤的。這才叫權衡!自動的隨時隨地、不間斷的進行監(jiān)視、測量你的項目性能,這是性能優(yōu)化成熟的標志,因為你的環(huán)境和需求是一直在變化的。
最后重點推薦一個性能優(yōu)化的案例學習網(wǎng)站 WPO Stats,這個網(wǎng)站上有很多很不錯的性能優(yōu)化的案例分享。