前端性能優(yōu)化一直是很多同學(xué)非常關(guān)注的問題,在日常的面試中也是經(jīng)常會被用到的點(diǎn)。今天就來了解一下前端性能優(yōu)化方案。
一:頁面渲染相關(guān)
01:減少頁面重繪和回流
回流(reflow):是指由于DOM結(jié)構(gòu)或樣式發(fā)生改變,瀏覽器需要重新計算元素的幾何屬性,然后重新布局頁面的過程
重繪(repaint):是指當(dāng)元素樣式發(fā)生改變,但不影響布局時,瀏覽器只需要重新繪制受影響的部分,而不需要重新計算布局
- 盡量減少使用 CSS 屬性的快捷方式:例如,使用 border-width、border-style 和 border-color 而不是 border。CSS 屬性的快捷方式會將所有值初始化為“初始值”,因此避免使用它們可以最小化重繪和回流(在實際工作中,CSS 快捷方式的性能影響微乎其微,并且使用快捷方式可以簡化樣式并解決一些樣式覆蓋問題)。
- 在 GPU 上渲染動畫:瀏覽器已經(jīng)優(yōu)化了 CSS 動畫,使其適用于觸發(fā)動畫屬性的重繪(因此也包括回流)。為了提高性能,將具有動畫效果的元素移動到 GPU 上??梢杂|發(fā) GPU 硬件加速的 CSS 屬性包括 transform、filter、will-change 和 position:fixed。動畫將在 GPU 上處理,提高性能,特別是在移動設(shè)備上(但避免過度使用,因為可能會導(dǎo)致性能問題)。
- 使用 will-change CSS 屬性來提高性能:它通知瀏覽器元素需要修改的屬性,使瀏覽器能夠在實際更改之前進(jìn)行優(yōu)化(但避免過度使用 will-change;在動畫中遇到性能問題時考慮使用它)。
- 通過更改 className 批量修改元素樣式。
- 將復(fù)雜的動畫元素定位為 fixed 或 absolute 以防止回流。
- 避免使用表格布局:因為在表格元素上觸發(fā)回流會導(dǎo)致其中所有其他元素的回流。
- 使用 translate 而不是修改 top 屬性來上下移動 DOM 元素。
- 創(chuàng)建多個 DOM 節(jié)點(diǎn)時,使用 DocumentFragment 進(jìn)行一次性創(chuàng)建。
- 必要時為元素定義高度或最小高度:沒有顯式高度,動態(tài)內(nèi)容加載可能會導(dǎo)致頁面元素移動或跳動,從而導(dǎo)致回流(例如,為圖像定義寬度和高度以防止布局變化并減少回流)。
- 盡量減少深度嵌套或復(fù)雜選擇器的使用,以提高 CSS 渲染效率。
- 對元素進(jìn)行重大樣式更改時,暫時使用 display:none 隱藏它們,進(jìn)行更改,然后將它們設(shè)置回 display:block。這樣可以最小化回流,只需兩次即可。
- 使用 contain CSS 屬性將元素及其內(nèi)容與文檔流隔離,防止其邊界框外意外副作用的發(fā)生。
02:圖像壓縮、切片和精靈
- 圖像壓縮: 圖像壓縮至關(guān)重要。許多圖像托管工具提供內(nèi)置的壓縮功能。如果需要手動壓縮圖像,可以使用像TinyPNG這樣的工具來幫助。
- 圖像切片: 當(dāng)顯示大型圖像,例如實時渲染,并且UI設(shè)計師不允許壓縮時,考慮將圖像切片并使用CSS布局將其拼合在一起。
- 精靈圖: 與圖像切片不同,精靈圖涉及將許多小圖像合并成一個大圖像。這樣,在加載頁面時,只需要獲取一個圖像來顯示多個頁面元素。這可以顯著提高頁面加載速度。然而,當(dāng)調(diào)整頁面布局時,精靈圖可能難以維護(hù)。
03:字體文件壓縮
在開發(fā)諸如特定活動的移動網(wǎng)頁等項目時,通常會使用@font-face接口來導(dǎo)入字體文件以實現(xiàn)更豐富的排版效果。然而,完整的字體文件往往有 幾M 大小。加載這樣的文件可能會阻塞頁面渲染,導(dǎo)致文字顯示延遲。
解決方案: 可以使用Font-spider從字體文件中提取出只有必要的字符。
04:延遲加載/預(yù)加載資源
- 懶加載: 懶加載是指僅在圖像進(jìn)入瀏覽器視口時加載圖像。圖像最初設(shè)置為占位符(通常是 1x1px 圖像)。加載圖像的決定基于圖像的 offsetTop 減去頁面的 scrollTop 是否小于或等于瀏覽器視口的 innerHeight。
- 預(yù)加載: 資源提示,包括預(yù)連接、預(yù)加載、預(yù)獲取等,允許您指定要提前加載的資源。這可以基于當(dāng)前頁面或應(yīng)用程序狀態(tài)、用戶行為或會話數(shù)據(jù)。這些資源提示方法可以通過預(yù)加載必要的資源來增強(qiáng)性能。實現(xiàn)選項包括使用鏈接標(biāo)簽進(jìn)行 DNS 預(yù)取、子資源、預(yù)加載、預(yù)獲取、預(yù)連接、預(yù)渲染,以及使用本地存儲進(jìn)行本地存儲。
二:捆綁優(yōu)化
01:Webpack 用于 resolve.alias 的優(yōu)化(同樣適用于 Vite)
resolve.alias配置將原始導(dǎo)入路徑映射到新的導(dǎo)入路徑,具有兩個目的:
- 創(chuàng)建別名
- 減少查找時間。
例如:
resolve: {
alias: {alias: {
‘vue$’: ‘vue/dist/vue.esm.js’,
‘@’: resolve(‘src’),
}
}
02:Webpack對解決方案的優(yōu)化(也適用于 Vite)
resolve.extensions 表示要嘗試的文件擴(kuò)展名列表。它還會影響構(gòu)建性能,并默認(rèn)為:
extensions: ['.js', '.json']'.js', '.json']
例如,當(dāng)遇到像 require('./data') 這樣的導(dǎo)入語句時,Webpack 首先會查找 ./data.js。如果未找到,它將搜索 ./data.json,如果仍未找到,它將拋出錯誤。
因此,建議盡量保持?jǐn)U展名列表盡可能簡潔,省略不太可能出現(xiàn)的情況。將最常用的文件擴(kuò)展名放在最前面,以加快搜索過程。
resolve: {
extensions: ['.js', '.vue', '.json'],'.js', '.vue', '.json'],
}
03:webpack 限定 loader 的加載范圍(不適用于 Vite)
加載器(loader)可能會消耗大量性能,因此在配置加載器時,可以使用 include 和 exclude 來限制范圍,從而優(yōu)化性能。
例如:
{
test: /\.svg$/,test: /\.svg$/,
loader: ‘svg-sprite-loader’,
include: [resolve(‘src/icons’)],
}
04:使用 webpack || Vite 拆分代碼
在沒有任何配置的情況下,Webpack 4會自動處理代碼拆分。入口文件的依賴項被捆綁到main.js中,而大于30kb的第三方包(例如echarts、xlsx、dropzone)被單獨(dú)捆綁到獨(dú)立的包中。其他異步加載的頁面或組件成為單獨(dú)的塊。
Webpack內(nèi)置的代碼拆分策略:
- 新塊是否來自共享或node_modules。
- 新塊在壓縮前的大小是否大于30kb。
- 異步加載塊的并發(fā)請求計數(shù)是否小于或等于5。
- 初始頁面加載的并發(fā)請求計數(shù)是否小于或等于3。
我們可以根據(jù)項目的需要調(diào)整配置。以下是Webpack代碼拆分的示例配置:
splitChunks({
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: ‘initial’,
},
dll: {
name: `chunk-dll`,
test: /[\\/]bizcharts|[\\/]\@antv[\\/]data-set/,
priority: 15,
chunks: ‘a(chǎn)ll’,
reuseExistingChunk: true
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: ‘a(chǎn)ll’,
reuseExistingChunk: true
},
}
})
對于 Vite 同樣可以執(zhí)行對應(yīng)拆分操作:
build: {
rollupOptions: {
output: {
chunkFileNames: ‘js/[name]-[hash].js’,
entryFileNames: ‘js/[name]-[hash].js’,
assetFileNames: ‘[ext]/[name]-[hash].[ext]’
}
}
}
05:Tree Shaking(搖樹優(yōu)化)
Tree shaking 是一種從項目中刪除未使用代碼的技術(shù)。它依賴于 ES 模塊語法。例如,當(dāng)使用 lodash 時:
// Bad: This imports the entire lodash library
import _ from 'lodash';
// Good: This imports only the 'isEmpty' function from lodash
import _isEmpty from 'lodash/isEmpty';
Tree Shaking 在很大程度上減少了捆綁包的大小,是性能優(yōu)化的重要部分。 Vite 和 Webpack 4.x 默認(rèn)情況下都啟用了 Tree Shaking。
06:在 Vite 中禁用不必要的構(gòu)建配置
Vite 還提供了優(yōu)化構(gòu)建配置的選項。以下是禁用某些構(gòu)建功能的示例:
build: {
terserOptions: {
compress: {
// Remove console in production// Remove console in production
drop_console: true,
drop_debugger: true,
},
},
// Disable file size reporting
reportCompressedSize: false,
// Disable source map generation to reduce bundle size (important for production)
sourcemap: false, // Disable this for production to minimize bundle size
}
三:整體優(yōu)化
01:服務(wù)端渲染(SSR)
服務(wù)器端渲染(SSR)是指在服務(wù)器上完成的渲染過程。最終渲染的HTML頁面通過HTTP協(xié)議發(fā)送到客戶端。在SEO和首次加載速度方面,SSR提供了顯著的好處。
- Vue:可以通過 Nuxt.js 實現(xiàn)
- React:可以通過 Next.js 實現(xiàn)
02:啟用GZIP壓縮
Gzip壓縮通過壓縮文件顯著提高了首次加載速度。使用Gzip可以將文本文件壓縮至原始大小的至少40%。不過,需要注意的是圖像文件不應(yīng)該使用Gzip壓縮。
在構(gòu)建過程中設(shè)置Gzip壓縮的步驟如下:
- 在Vue項目中安裝Gzip壓縮所需的依賴,并將productionGzip設(shè)置為true以啟用Gzip壓縮。
npm install --save-dev compression-webpack-plugin--save-dev compression-webpack-plugin
- 修改構(gòu)建命令以使用Gzip壓縮。不過需要注意,這可能會遇到錯誤“ValidationError: Compression Plugin Invalid Options”。為了解決這個問題,根據(jù)官方文檔將設(shè)置從“asset”更改為“filename”。
- 再次運(yùn)行構(gòu)建命令 npm run build,你將生成 .gz 文件,表示成功壓縮。
03:Brotli 壓縮
Brotli是開源的一種新型壓縮算法(2015 年 Google 推出,Github 地址:https://github.com/google/brotli ),Brotli壓縮比Gzip壓縮性能更好。開啟Brotli壓縮功能后,CDN節(jié)點(diǎn)會對資源進(jìn)行智能壓縮后返回,縮小傳輸文件大小,提升文件傳輸效率,減少帶寬消耗。
啟用Brotli壓縮可以將CDN流量額外減少20%,相較于Gzip。在各種情況下,Brotli的性能比Gzip高出17-25%,特別是當(dāng)設(shè)置為級別1時,超過了Gzip級別9的壓縮效果。(數(shù)據(jù)來自 Google 數(shù)據(jù)報告:https://quixdb.github.io/squash-benchmark/unstable/
)
大多數(shù)主流瀏覽器都支持Brotli壓縮,除了Internet Explorer和Opera Mini。
要在Vite項目中啟用Brotli壓縮,可以使用vite-plugin-compression插件。修改您的.env.production文件,相應(yīng)地設(shè)置VITE_COMPRESSION全局變量。
# 默認(rèn)情況下禁用壓縮:#默認(rèn)情況下禁用壓縮:
VITE_COMPRESSION = "none"
# 以下配置支持在不刪除原始文件的情況下進(jìn)行壓縮:
Enable Gzip compression:
VITE_COMPRESSION = "gzip"
# 啟用Brotli壓縮:
VITE_COMPRESSION = "brotli"
# 同時啟用GZIP和Brotli壓縮:
VITE_COMPRESSION = "both"
# 以下配置啟用壓縮和刪除原始文件:
Enable Gzip compression:
VITE_COMPRESSION = "gzip-clear"
# 啟用Brotli壓縮:
VITE_COMPRESSION = "brotli-clear"
# 同時啟用GZIP和Brotli壓縮:
VITE_COMPRESSION = "both-clear"
04:緩存問題
緩存是一種基本的優(yōu)化技術(shù),通過減少IO操作和CPU計算來提高性能。性能優(yōu)化的第一條規(guī)則是優(yōu)先考慮緩存。
緩存選項包括瀏覽器緩存、CDN緩存、反向代理、本地緩存、分布式緩存和數(shù)據(jù)庫緩存。
05:Ajax 緩存
Ajax 緩存請求的 URL 和響應(yīng)結(jié)果,以提高頁面響應(yīng)速度和用戶體驗。在進(jìn)行 Ajax 請求時,盡量使用 GET 方法,以利用客戶端緩存并增強(qiáng)請求速度。
06:組件導(dǎo)入
使用第三方組件庫時,只導(dǎo)入必要的組件,例如
import { Button } from ‘vant’
07:動態(tài)加載
使用import()在需要時動態(tài)導(dǎo)入第三方庫和組件。例如,在測試環(huán)境中,只有當(dāng)主機(jī)域名不是生產(chǎn)域名時才啟用VConsole調(diào)試。
if (location.host !== ‘production-domain’) {
import(‘@/utils/vconsole’);import(‘@/utils/vconsole’);
}
08:異步組件加載
使用 import 或 require 方法異步加載組件:
() => import(‘@/pages/xxx.vue’)
resolve => require(['@/pages/xxx.vue'], resolve)
09:懶加載路由
懶加載路由是在路由配置中應(yīng)用異步組件加載的一種方式:
{
path: '/index','/index',
name: 'index',
component: () => import('@view/xxx.vue'),
meta: { title: 'Homepage' }
}
10:CDN(內(nèi)容分發(fā))
內(nèi)容傳送網(wǎng)絡(luò)(CDN)將靜態(tài)文件、音頻、視頻、JavaScript 資源、圖片等分發(fā)到全球各地的服務(wù)器。通過從附近的 CDN 服務(wù)器提供資源,可以減少延遲并提高加載速度。
11:域名分片(Domain sharding)
由于瀏覽器限制了每個域(domain)的活動連接數(shù)。為了可以同時下載超過該限制數(shù)的資源,域名分片(domain sharding)會將內(nèi)容拆分到多個子域中。當(dāng)使用多個域來處理多個資源時,瀏覽器能夠同時下載更多資源,從而縮短了頁面加載時間并改善了用戶體驗。
12:DNA預(yù)取
DNS-prefetch 嘗試在請求資源之前解析域名
當(dāng)瀏覽器從(第三方)服務(wù)器請求資源時,必須先將該跨源域名解析為 IP 地址,然后瀏覽器才能發(fā)出請求。此過程稱為 DNS 解析。雖然 DNS 緩存可以幫助減少此延遲,但 DNS 解析可能會給請求增加明顯的延遲。對于打開了與許多第三方的連接的網(wǎng)站,此延遲可能會大大降低加載性能。
dns-prefetch 可幫助開發(fā)人員掩蓋 DNS 解析延遲。
HTML 元素通過設(shè)置 rel 屬性值為 dns-prefetch 提供此功能
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<!-- dns-prefetch -->
<link rel="dns-prefetch" />
<!-- 其他 head 元素 -->
</head>
<body>
<!-- 頁面內(nèi)容 -->
</body>
</html>
13:Web Workers
Web Workers 為 JavaScript 創(chuàng)建了一個多線程環(huán)境,允許任務(wù)在后臺運(yùn)行而不阻塞主線程。對于計算密集型任務(wù),使用 Web Workers 可以優(yōu)化性能。