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