一直想寫篇前端性能相關的總結,個人覺得這塊的內容會比較分散,面試的時候問起來,也不容易有一個清晰的框架,但是平時的習慣是想起來什麼就寫什麼,所以攢了好久的內容只能躺在一堆筆記草稿裏面;
---🚩🚩正文分割線🚩🚩---
按頁面加載鏈路分類,從下面幾個方面開始
- 首屏加載
- 代碼優化
- 構建工具
首屏加載
這部分其實就是把從獲取資源到頁面呈現中可以優化的點提取出來
1. DNS預解析
不需要用户點擊鏈接就在後台解析,在head中添加
<link rel="dns-prefetch" href="//example.com">
但是要注意會增加一定的網絡請求和帶寬消耗,非必要域名謹慎使用
2. 開啓HTTP2
首先説下相對於HTTP1的優勢
- 多路複用,能夠在單個
TCP連接上同時傳輸多個請求和響應;HTTP1.1有一個可選的Pipelining技術,但它是按照順序處理響應的,後發的請求可能被先發的請求阻塞,所以很多瀏覽器默認不開啓。 - 首部壓縮,使用HPACK算法對請求和響應頭部進行壓縮,減少了首部大小,節省了帶寬。而在
HTTP/1.x中,每次請求都需要發送完整的頭部信息,很容易造成不必要的帶寬浪費。 - 服務器推送,服務端可以在發送頁面
HTML,也就是客户端請求對應HTML頁面時主動推送其它資源,而不用等到瀏覽器解析到相應位置,發起請求再響應。 -
二進制分幀,使用二進制協議對數據進行分幀傳輸。二進制協議更高效,減少了解析數據的開銷,並提高了傳輸速度。
在nginx中開啓HTTP2
# 修改nginx.conf中的配置
server {
listen 443 ssl http2;
server_name example.com;
# SSL證書和密鑰路徑
ssl_certificate /path/to/ssl/cert;
ssl_certificate_key /path/to/ssl/key;
# 其他配置項
}
3. 資源的預加載
這個其實是安排資源以更高的優先級進行下載和緩存,更詳細的可以看看MDN文檔
<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="main.js" as="script" />
4. 動態創建加載腳本
不管是js還是css,在下載過程中其實都是會阻塞頁面的,動態加載會在下載資源的同時,也不影響後續代碼的執行
const script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);
5. 同構渲染
其實就是服務端渲染(SSR)+ 客户端渲染(CSR),服務端渲染的升級版,像現在的Nuxt.js或者Next.js就可以實現;
引用《vue.js設計與實現》的對比來更直觀的瞭解同構渲染的特點
| CSR | SSR | 同構 | |
|---|---|---|---|
| SEO | 不友好 | 友好 | 友好 |
| 白屏問題 | 有 | 無 | 無 |
| 佔用服務器資源 | 少 | 多 | 中 |
| 用户體驗 | 好 | 差 | 好 |
6. 可見性優化
這部分主要是針對非可視區域進行延遲加載來減少首屏執行的邏輯
非可視區域
- 延遲接口請求,使用
setTimeout或者then函數來置後加載時機; - 圖片懶加載,使用
IntersectionObserver實現可視區域判斷;
虛擬滾動
只加載上下及當前頁的數據;可以通過滾動時分頁或者vue-virtual-scroll-list及react-virtualized一類的插件實現;
7. 針對白屏/抖動
加載過程中無法避免的會有短暫白屏,
- 骨架屏,可以選擇固定灰色塊,或者計算頁面元素寬高生成灰色快;
- loading,加入比較有意思的
loading動畫; - 定義寬高,圖片或者接口數據在渲染到頁面之後,會撐開所在的元素,就造成頁面抖動,設置好盒子或者圖片的寬高或者設置個佔位;
- 字體閃爍,加載字體且生效之前的閃爍,通過壓縮字體減小資源體積,設置
font-display:block來解決加載過程中的字體樣式異常;
8. 靜態資源
- 合理使用協商緩存和強緩存及本地存儲
- 使用字體圖標代替圖片圖標
- 使用webp
- 圖片壓縮
- 使用cdn
代碼優化
這部分只是列出來可優化點,感興趣可以去搜索相關實現,建議只有出現明確的性能問題存在時,才進行優化
1. JS
- 使用
script的async和defer屬性避免阻塞; - service worker,攔截網絡請求,靈活的判斷是否需要緩存資源;
- Web Worker,創建一個新的線程,在一個獨立的js環境中執行邏輯,不會阻塞後續邏輯的執行,針對耗時的計算任務或者執行時間比較久的邏輯處理;
- 批量請求及事件任務切片;
- 節流和防抖;
- 事件委託;
- 及時銷燬閉包及定時器;
- 緩存變量及dom屬性;
- 變量作用域的合理聲明;
2. CSS
- 迴流屬性放在一塊集中修改;
- 避免選擇器嵌套過深;
- 用
CSS動畫代替JS動畫; - 使用偽元素簡化
html結構,如:before代替div; - 開啓GPU加速,這個非必要不推薦開啓;
- 減少
CSS類名查找範圍,瀏覽器解析CSS遵循的是從右到左的查找規範,先找.b再找.a,將.wrap .a .b改為wrap .b;
3. Vue
v-show和v-if的合理使用,頻繁更新顯示狀態使用v-show;- 使用
keep-alive和v-once減少多餘的更新渲染; - 通過
Object.freeze移除雙向綁定,減少不必要的數據監聽; - 避免
template中使用複雜的表達式;
4. React
- memo,減少子組件的重複渲染,簡單組件不會有太大的效果,並且會加大內存消耗;
- useMemo,相當於Vue中的computed函數,所設置的依賴沒有變化時,就會返回上一次的計算結果;
- useCallback,避免重複創建函數;
- 組件卸載清理,Class組件:
componentWillUnmount,Function 組件:useEffect return - 使用React Fragment,減少額外節點的渲染;
構建工具
目前基本上使用vite,這裏主要針對vite優化,webpack就簡單帶過
1. webpack
- 指定模塊解析範圍,設置解析文件類型範圍
- webpack打包/構建緩存
hard-source-webpack-plugin; - 資源的壓縮,拆分、第三方包的提取合併(
config配置optimization.splitChunks);
2. 搖樹優化
就是在保證代碼運行結果不變的前提下,去除無用的代碼;其實Rollup會默認開啓搖樹優化,但需要是ES6 module模塊,第三方包儘管可能使用esm版本,本身體積會更小,而且能有更好的壓縮效果
import { cloneDeep } from 'lodash'
// 改為
import { cloneDeep } from 'lodash-es'
const obj = cloneDeep({}) // 如果這行被註釋,vite就不會再引入lodash包
刪除線上的console和debugger,這個根據項目需求決定是否需要配置
{
esbuild: {
drop: ['console', 'debugger'],
}
}
3. gzip壓縮
這個就是在客户端進行文件壓縮,服務端直接調用
import viteCompression from 'vite-plugin-compression';
viteCompression({
verbose: true,
disable: false, // 不禁⽤壓縮
deleteOriginFile: false, // 壓縮後是否刪除原⽂件
threshold: 10240, // 壓縮前最⼩⽂件⼤⼩
algorithm: 'gzip', // 壓縮算法
ext: '.gz', // ⽂件類型
}),
nginx配置靜態gzip壓縮,會直接讀取文件夾中.gz文件
# 修改nginx.conf中的配置
http {
gzip_static on;
}
Content-Encoding為gzip就表示設置成功了
4. 圖片壓縮
import viteImagemin from 'vite-plugin-imagemin'
viteImagemin({
gifsicle: { // gif圖片壓縮
optimizationLevel: 3, // 選擇1到3之間的優化級別
interlaced: false, // 隔行掃描gif進行漸進式渲染
// colors: 2 // 將每個輸出GIF中不同顏色的數量減少到num或更少。數字必須介於2和256之間。
},
optipng: { // png
optimizationLevel: 7, // 選擇0到7之間的優化級別
},
mozjpeg: {// jpeg
quality: 20, // 壓縮質量,範圍從0(最差)到100(最佳)。
},
pngquant: {// png
quality: [0.8, 0.9], // Min和max是介於0(最差)到1(最佳)之間的數字,類似於JPEG。達到或超過最高質量所需的最少量的顏色。如果轉換導致質量低於最低質量,圖像將不會被保存。
speed: 4, // 壓縮速度,1(強力)到11(最快)
},
svgo: { // svg壓縮
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
},
})
5. 依賴分析
基本上都是用這種方式來查找不必要的依賴引用來減小包體積
import { visualizer } from 'rollup-plugin-visualizer';
const command = process.env.npm_lifecycle_event
{
plugins: [
command === 'report' ?
visualizer({ open: true, brotliSize: true, filename: 'report.html' })
: null
]
}
最後
從整體架構方面還可以做下面幾件事;
- 組件增加權重,針對單個組件,給組件添加權重值,針對權重大的組件優先展示;
- 按機型加載資源,根據當前系統版本、機型配置做不同的資源加載,動畫交互降級;
- 微前端 拆分應用,剝離業務,減少業務之間的關聯影響,使用
micro-app或者qiankun;
最後優化是有成本的,也需要根據場景決定是否進行優化
傳送門
萬字長文:分享前端性能優化知識體系