前端效能優化
首先思考 從輸入 URL 到頁面載入完成,發生了什麼?
-
- DNS 解析
-
- TCP連線
-
- HTTP請求
-
- HTTP響應
-
- 瀏覽器得到響應資料、解析、渲染
由此可知,可以從以上幾個過程中考慮如何優化。而前兩步的優化往往需要伺服器端developer協作完成,前端單方面的可作為空間有限,那麼就從http請求到得到相應渲染頁面這一過程來考慮前端效能優化。
HTTP請求
優化方向為以下兩種:
- 減少請求次數--資源合併
- 減少單次請求所花費的時間--資源壓縮
以上兩個方向直指資源的合併與壓縮,也就是構建工具webpack做的事情。可通過webpack以及相關外掛進行優化。
javascript/css相關--構建層面
- 利用CommonsChunkPlugin(webpack 4中被optimization.splitChunks 和 optimization.runtimeChunk替代)實現分包,把公用程式碼抽取出來,利用瀏覽器快取來避免多次請求
- 利用DllPlugin拆分資源
- 利用Tree-Shaking刪除冗餘程式碼
javascript/css相關--程式碼層面
- link css放到head中,因為CSSOM和DOM都構建完成才能合力構建渲染樹,將css放到head就是為了儘早構建CSSOM,防止阻塞渲染
- 首屏快速顯示可使用critical css,即 將首屏用到的css單拎為style 標籤放到head ,其他css後置並設定rel="preload" 屬性,rel="preload" 通知瀏覽器開始獲取非關鍵CSS以供之後用。其關鍵在於,preload 不阻塞渲染,無論資源是否載入完成,瀏覽器都會接著繪製頁面,可以使用Node.js的模組Critical 來實現
- javascript會阻塞瀏覽器,因此要放到html尾部,或者按需使用defer(非同步載入,整個文件解析完、DOMContentLoaded事件即將被觸發時執行)或async(非同步載入,該檔案載入結束後立即執行)。實際上並不是javascript阻塞了瀏覽器,而是因為js引擎是獨立於渲染引擎存在的,遇到js時,瀏覽器會將控制權交給js引擎,渲染引擎沒權了自然無法繼續工作
- 非同步模組載入,如將vue-router中的component從方案一改為方案二(利用function形式,在用到的時候返回相關元件)
//方案一 import Home from '@/view/home/index' ... component: Home //方案二 const Home = () => import('@/view/home/index'); ... component: Home 複製程式碼
圖片相關
首先看下不同型別的圖片的特性:
JPG
- 優點:最大的特點是有失真壓縮,壓縮至原有體積的**50%以下時,JPG 仍然可以保持住 60%**的品質,JPG 格式以 24 位儲存單個圖,可以呈現多達 1600 萬種顏色,足以應對大多數場景下對色彩的要求,這一點決定了它壓縮前後的質量損耗並不容易被我們人類的肉眼所察覺。
- 缺點:處理線條感強或者顏色對比強烈的影象時會有明顯的模糊,且不支援透明度處理
- 適用場景:大的背景圖、輪播圖或 Banner 圖
PNG
- 優點:PNG是無失真壓縮的高保真的圖片格式,比 JPG 更強的色彩表現力,對線條的處理更加細膩,對透明度有良好的支援
- 缺點:體積太大
- 適用場景:小的 Logo、顏色簡單且對比強烈的圖片或背景
SVG
- 優點:文字檔案、體積小、圖片可無限放大而不失真、相容性好,可壓縮性更強 。svg 基於 XML 語法的影象格式,與 jpg 和 png 不同處在於,svg 不是基於畫素點,而是是基於對影象的形狀描述。
- 缺點:渲染成本比較高,有學習成本(因為它是可程式設計的)
- 適用場景:常用於簡單影象,需要修改或編輯等靈活使用的場景
Base64
- 優點:文字檔案、依賴編碼、小圖示解決方案 ,Base64 並非一種圖片格式,而是一種編碼方式,瀏覽器可直接解析,無需傳送http請求
- 缺點:通過base64編碼後,圖片大小會變為原來的4/3
- 適用場合:2kb以下的小圖,更新頻率很低,無法以雪碧圖形式使用
WebP
- 優點:分無失真壓縮和有失真壓縮,支援透明,支援動圖,同等質量下size更小
- 缺點:相容性差,增加伺服器負擔(與jpg相比,編碼同質量的webP檔案會佔用更多的計算資源)
- 適用場景:不需相容,或做降級處理(修改圖片字尾,支援webP的使用webP,不支援的使用其他常規圖片)
相關效能優化
- 為了體驗好,可以將輪播圖的首屏以base64形式展示,瀏覽器可直接解析無需http請求
- 多個小圖拼接雪碧圖,減少http請求
- 保證視覺無影響的情況下壓縮圖片到最小
- 上傳至服務端的圖按照具體場景選擇不同的質量壓縮,如上傳合同照片需要清晰度較高故質量指數要高,上傳小頭像則無需很高的質量指數
- 多圖時懶載入,如類似相簿模式瀏覽圖片時,只給當前圖及其後3個依次新增src屬性,避免一次請求過多的圖片,多排圖片豎屏滾動時,只在圖片進入或即將進入視口時請求圖片
- 合適的場景用合適的圖片格式,如大圖、輪播圖用jpg等,避免無謂的浪費
http攜帶資料相關
- Gzip 壓縮:HTTP 壓縮的一種,對HTTP內容進行重新編碼的過程。Gzip 壓縮背後的原理,是在一個文字檔案中找出一些重複出現的字串、臨時替換它們,從而使整個檔案變小。
- cookie 中僅攜帶與伺服器互動用的資料,純儲存的資料交給webStorage
- 靜態資源放到cdn上,除了能更快獲取到資源外,還能避免不需要的cookie飛來飛去
頁面資料儲存相關
- 利用瀏覽器快取(Memory Cache,Service Worker Cache,HTTP Cache,Push Cache)
- 利用本地儲存(cookie,4k,來往於C/S;sessionStorage,5-10M,會話級別;localStorage,5-10M,永久儲存;indexDB,無上限,永久儲存)
渲染
瀏覽器執行機制
- HTML parse:解析html文件輸出DOM樹 ,並在此過程中發出了頁面渲染所需外部資源的http請求
- Style:識別和載入CSS,解析CSS形成CSSOM ,CSSOM 的解析過程與DOM 的解析過程是並行的,二者都解析完畢後形成渲染樹
- Layout:計算頁面中所有元素的位置資訊和大小資訊
- Paint:將每個頁面圖層轉換成畫素,並對所有的媒體檔案進行解碼
- Composit:合併各個圖層,將資料由CPU輸出給GPU繪製到螢幕上
之後每當有新元素加到DOM樹種,瀏覽器會通過CSS引擎查詢CSS樣式表,找到符合的樣式應用到該元素然後重新繪製。由此可以理出第一個可優化的點--CSS樣式表優化以使其加快查詢速度,此處要注意,CSS是從右往左匹配的。因此CSS程式碼方面可有以下優化點:
- 避免使用萬用字元
- 儘量避免使用標籤選擇器
- 避免重複匹配和重複定義
- 減少巢狀
此外,由於js引擎和渲染引擎是獨立的,二者要跨界交流就會依賴橋接介面,所以當js操作引起dom變化時,過橋費很昂貴,頻繁過橋就愈加昂貴,所以應該避免不必要的過橋,要懂得讓js為DOM分壓 ,DOM Fragment就是這個思路。綜上,相關的優化有以下幾點:
- 避免頻繁操作DOM、過渡渲染
//避免以下方式 for(let i = 0; i < 100; i++) { document.getElementById('test').innterHTML += '<span>test</span>' } //推薦以下方式 let test = document.getElementById('test'); //快取結果,減少呼叫DOM介面就省了過橋費 let content = ''; for(let i = 0; i < 100; i++) { content += '<span>test</span>' } test.innerHTML = content; //減少DOM更改,可減少由此觸發的重繪或迴流 複製程式碼
重繪和迴流
重繪 :當我們對 DOM 的修改導致了樣式的變化、卻並未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪製新的樣式
迴流 :也叫重排,當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(width、height、padding、margin、left、top、border等)、或增減元素時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然後再將計算的結果繪製出來。用到這些屬性offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight時,也會立即觸發迴流。
因此避免重繪和迴流 也是經典的優化點,如何避免呢?
- 將上述的會觸發迴流的屬性快取起來,避免不必要的重複觸發
- 避免逐條改動樣式,最好通過class一次性改動
- 有複雜改動時將DOM離線,即將DOM先隱藏,複雜改變結束後再顯示
- 需要頻繁變更的元素使用脫離文件流的樣式
節流和防抖
此外,針對操作或其他的節流和防抖 也是十分非常特別相當必要的。
節流 :在某段時間內,不管你觸發了多少次回撥,都只認第一次,並在計時結束時給予響應
function throttle(fn, delay = 0) { let timer = null; return function() { let context = this; if(!timer) { timer = setTimeout(() => { timer = null; }, delay) return fn.apply(context, arguments) // 加return是因為fn可能會有返回值 } } } 複製程式碼
防抖 :在某段時間內,不管你觸發了多少次回撥,都只認最後一次,並在計時結束時給予響應
function debounce(fn, delay = 0) { let timer = null; return function() { if(timer) { clearTimeout(timer); } timer = setTimeout(() => { return fn.apply(this, arguments) }, delay) } } 複製程式碼