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