寫給中高級(jí)前端關(guān)于性能優(yōu)化的9大策略和6大指標(biāo) | 網(wǎng)易四年實(shí)踐
「鏈接和長(zhǎng)圖失效,請(qǐng)大家點(diǎn)擊閱讀原文查看詳情」
前言
筆者近半年一直在參與項(xiàng)目重構(gòu),在重構(gòu)過(guò)程中大量應(yīng)用「性能優(yōu)化」和「設(shè)計(jì)模式」兩方面的知識(shí)。「性能優(yōu)化」和「設(shè)計(jì)模式」兩方面的知識(shí)不管在工作還是面試時(shí)都是高頻應(yīng)用場(chǎng)景,趁著這次參與大規(guī)模項(xiàng)目重構(gòu)的機(jī)會(huì),筆者認(rèn)真梳理出一些常規(guī)且必用的性能優(yōu)化建議,同時(shí)結(jié)合日常開發(fā)經(jīng)驗(yàn)整理出筆者在網(wǎng)易四年來(lái)實(shí)踐到的認(rèn)為有用的所有性能優(yōu)化建議,與大家一起分享分享!(由于篇幅有限,那設(shè)計(jì)模式在后面再專門出一篇文章唄)
可能有些性能優(yōu)化建議已被大家熟知,不過(guò)也不影響這次分享,當(dāng)然筆者也將一些平時(shí)可能不會(huì)注意的細(xì)節(jié)羅列出來(lái)。
平時(shí)大家認(rèn)為性能優(yōu)化是一種無(wú)序的應(yīng)用場(chǎng)景,但在筆者看來(lái)它是一種有序的應(yīng)用場(chǎng)景且很多性能優(yōu)化都是互相鋪墊甚至一帶一路。從過(guò)程趨勢(shì)來(lái)看,性能優(yōu)化可分為「網(wǎng)絡(luò)層面」和「渲染層面」;從結(jié)果趨勢(shì)來(lái)看,性能優(yōu)化可分為「時(shí)間層面」和「體積層面」。簡(jiǎn)單來(lái)說(shuō)就是要在訪問(wèn)網(wǎng)站時(shí)使其快準(zhǔn)狠地立馬呈現(xiàn)在用戶眼前。
所有的性能優(yōu)化都圍繞著兩大層面兩小層面實(shí)現(xiàn),核心層面是網(wǎng)絡(luò)層面和渲染層面,輔助層面是時(shí)間層面和體積層面,而輔助層面則充滿在核心層面里。于是筆者通過(guò)本文整理出關(guān)于前端性能優(yōu)化的「九大策略」和「六大指標(biāo)」。當(dāng)然這些策略和指標(biāo)都是筆者自己定義,方便通過(guò)某種方式為性能優(yōu)化做一些規(guī)范。
因此在工作或面試時(shí)結(jié)合這些特征就能完美地詮釋性能優(yōu)化所延伸出來(lái)的知識(shí)了。「前方高能,不看也得收藏,走起!??!」
所有代碼示例為了凸顯主題,只展示核心配置代碼,其他配置并未補(bǔ)上,請(qǐng)自行腦補(bǔ)
九大策略
網(wǎng)絡(luò)層面
「網(wǎng)絡(luò)層面」的性能優(yōu)化,無(wú)疑是如何讓資源體積更小加載更快,因此筆者從以下四方面做出建議。
-
「構(gòu)建策略」:基于構(gòu)建工具( Webpack/Rollup/Parcel/Esbuild/Vite/Gulp) -
「圖像策略」:基于圖像類型( JPG/PNG/SVG/WebP/Base64) -
「分發(fā)策略」:基于內(nèi)容分發(fā)網(wǎng)絡(luò)( CDN) -
「緩存策略」:基于瀏覽器緩存( 強(qiáng)緩存/協(xié)商緩存)
上述四方面都是一步接著一步完成,充滿在整個(gè)項(xiàng)目流程里。「構(gòu)建策略」和「圖像策略」處于開發(fā)階段,「分發(fā)策略」和「緩存策略」處于生產(chǎn)階段,因此在每個(gè)階段都可檢查是否按順序接入上述策略。通過(guò)這種方式就能最大限度增加性能優(yōu)化應(yīng)用場(chǎng)景。
構(gòu)建策略
該策略主要圍繞webpack做相關(guān)處理,同時(shí)也是接入最普遍的性能優(yōu)化策略。其他構(gòu)建工具的處理也是大同小異,可能只是配置上不一致。說(shuō)到webpack的性能優(yōu)化,無(wú)疑是從時(shí)間層面和體積層面入手。
筆者發(fā)現(xiàn)目前webpack v5整體兼容性還不是特別好,某些功能配合第三方工具可能出現(xiàn)問(wèn)題,故暫未升級(jí)到v5,繼續(xù)使用v4作為生產(chǎn)工具,故以下配置均基于v4,但總體與v5的配置出入不大
筆者對(duì)兩層面分別做出6個(gè)性能優(yōu)化建議總共12個(gè)性能優(yōu)化建議,為了方便記憶均使用四字詞語(yǔ)概括,方便大家消化。?表示減少打包時(shí)間,??表示減少打包體積。
-
「減少打包時(shí)間」: 縮減范圍、緩存副本、定向搜索、提前構(gòu)建、并行構(gòu)建、可視結(jié)構(gòu) -
「減少打包體積」: 分割代碼、搖樹優(yōu)化、動(dòng)態(tài)墊片、按需加載、作用提升、壓縮資源
?縮減范圍
「配置include/exclude縮小Loader對(duì)文件的搜索范圍」,好處是避免不必要的轉(zhuǎn)譯。node_modules目錄的體積這么大,那得增加多少時(shí)間成本去檢索所有文件???
include/exclude通常在各大Loader里配置,src目錄通常作為源碼目錄,可做如下處理。當(dāng)然include/exclude可根據(jù)實(shí)際情況修改。
export default {
// ...
module: {
rules: [{
exclude: /node_modules/,
include: /src/,
test: /\.js$/,
use: "babel-loader"
}]
}
};
?緩存副本
「配置cache緩存Loader對(duì)文件的編譯副本」,好處是再次編譯時(shí)只編譯修改過(guò)的文件。未修改過(guò)的文件干嘛要隨著修改過(guò)的文件重新編譯呢?
大部分Loader/Plugin都會(huì)提供一個(gè)可使用編譯緩存的選項(xiàng),通常包含cache字眼。以babel-loader和eslint-webpack-plugin為例。
import EslintPlugin from "eslint-webpack-plugin";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
},
plugins: [
new EslintPlugin({ cache: true })
]
};
?定向搜索
「配置resolve提高文件的搜索速度」,好處是定向指定必須文件路徑。若某些第三方庫(kù)以常規(guī)形式引入可能報(bào)錯(cuò)或希望程序自動(dòng)索引特定類型文件都可通過(guò)該方式解決。
alias映射模塊路徑,extensions表明文件后綴,noParse過(guò)濾無(wú)依賴文件。通常配置alias和extensions就足夠。
export default {
// ...
resolve: {
alias: {
"#": AbsPath(""), // 根目錄快捷方式
"@": AbsPath("src"), // src目錄快捷方式
swiper: "swiper/js/swiper.min.js"
}, // 模塊導(dǎo)入快捷方式
extensions: [".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] // import路徑時(shí)文件可省略后綴名
}
};
?提前構(gòu)建
「配置DllPlugin將第三方依賴提前打包」,好處是將DLL與業(yè)務(wù)代碼完全分離且每次只構(gòu)建業(yè)務(wù)代碼。這是一個(gè)古老配置,在webpack v2時(shí)已存在,不過(guò)現(xiàn)在webpack v4+已不推薦使用該配置,因?yàn)槠浒姹镜鷰?lái)的性能提升足以忽略DllPlugin所帶來(lái)的效益。
「DLL」意為動(dòng)態(tài)鏈接庫(kù),指一個(gè)包含可由多個(gè)程序同時(shí)使用的代碼庫(kù)。在前端領(lǐng)域里可認(rèn)為是另類緩存的存在,它把公共代碼打包為DLL文件并存到硬盤里,再次打包時(shí)動(dòng)態(tài)鏈接DLL文件就無(wú)需再次打包那些公共代碼,從而提升構(gòu)建速度,減少打包時(shí)間。
配置DLL總體來(lái)說(shuō)相比其他配置復(fù)雜,配置流程可大致分為三步。
首先告知構(gòu)建腳本哪些依賴做成DLL并生成DLL文件和DLL映射表文件。
import { DefinePlugin, DllPlugin } from "webpack";
export default {
// ...
entry: {
vendor: ["react", "react-dom", "react-router-dom"]
},
mode: "production",
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "all",
name: "vendor",
test: /node_modules/
}
}
}
},
output: {
filename: "[name].dll.js", // 輸出路徑和文件名稱
library: "[name]", // 全局變量名稱:其他模塊會(huì)從此變量上獲取里面模塊
path: AbsPath("dist/static") // 輸出目錄路徑
},
plugins: [
new DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development") // DLL模式下覆蓋生產(chǎn)環(huán)境成開發(fā)環(huán)境(啟動(dòng)第三方依賴調(diào)試模式)
}),
new DllPlugin({
name: "[name]", // 全局變量名稱:減小搜索范圍,與output.library結(jié)合使用
path: AbsPath("dist/static/[name]-manifest.json") // 輸出目錄路徑
})
]
};
然后在package.json里配置執(zhí)行腳本且每次構(gòu)建前首先執(zhí)行該腳本打包出DLL文件。
{
"scripts": {
"dll": "webpack --config webpack.dll.js"
}
}
最后鏈接DLL文件并告知webpack可命中的DLL文件讓其自行讀取。使用html-webpack-tags-plugin在打包時(shí)自動(dòng)插入DLL文件。
import { DllReferencePlugin } from "webpack";
import HtmlTagsPlugin from "html-webpack-tags-plugin";
export default {
// ...
plugins: [
// ...
new DllReferencePlugin({
manifest: AbsPath("dist/static/vendor-manifest.json") // manifest文件路徑
}),
new HtmlTagsPlugin({
append: false, // 在生成資源后插入
publicPath: "/", // 使用公共路徑
tags: ["static/vendor.dll.js"] // 資源路徑
})
]
};
為了那幾秒鐘的時(shí)間成本,筆者建議配置上較好。當(dāng)然也可使用autodll-webpack-plugin代替手動(dòng)配置。
?并行構(gòu)建
「配置Thread將Loader單進(jìn)程轉(zhuǎn)換為多進(jìn)程」,好處是釋放CPU多核并發(fā)的優(yōu)勢(shì)。在使用webpack構(gòu)建項(xiàng)目時(shí)會(huì)有大量文件需解析和處理,構(gòu)建過(guò)程是計(jì)算密集型的操作,隨著文件增多會(huì)使構(gòu)建過(guò)程變得越慢。
運(yùn)行在Node里的webpack是單線程模型,簡(jiǎn)單來(lái)說(shuō)就是webpack待處理的任務(wù)需一件件處理,不能同一時(shí)刻處理多件任務(wù)。
文件讀寫與計(jì)算操作無(wú)法避免,能不能讓webpack同一時(shí)刻處理多個(gè)任務(wù),發(fā)揮多核CPU電腦的威力以提升構(gòu)建速度呢?thread-loader來(lái)幫你,根據(jù)CPU個(gè)數(shù)開啟線程。
在此需注意一個(gè)問(wèn)題,若項(xiàng)目文件不算多就不要使用該性能優(yōu)化建議,畢竟開啟多個(gè)線程也會(huì)存在性能開銷。
import Os from "os";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "thread-loader",
options: { workers: Os.cpus().length }
}, {
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
}
};
?可視結(jié)構(gòu)
「配置BundleAnalyzer分析打包文件結(jié)構(gòu)」,好處是找出導(dǎo)致體積過(guò)大的原因。從而通過(guò)分析原因得出優(yōu)化方案減少構(gòu)建時(shí)間。BundleAnalyzer是webpack官方插件,可直觀分析打包文件的模塊組成部分、模塊體積占比、模塊包含關(guān)系、模塊依賴關(guān)系、文件是否重復(fù)、壓縮體積對(duì)比等可視化數(shù)據(jù)。
可使用webpack-bundle-analyzer配置,有了它,我們就能快速找到相關(guān)問(wèn)題。
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
export default {
// ...
plugins: [
// ...
BundleAnalyzerPlugin()
]
};
??分割代碼
「分割各個(gè)模塊代碼,提取相同部分代碼」,好處是減少重復(fù)代碼的出現(xiàn)頻率。webpack v4使用splitChunks替代CommonsChunksPlugin實(shí)現(xiàn)代碼分割。
splitChunks配置較多,詳情可參考官網(wǎng),在此筆者貼上常用配置。
export default {
// ...
optimization: {
runtimeChunk: { name: "manifest" }, // 抽離WebpackRuntime函數(shù)
splitChunks: {
cacheGroups: {
common: {
minChunks: 2,
name: "common",
priority: 5,
reuseExistingChunk: true, // 重用已存在代碼塊
test: AbsPath("src")
},
vendor: {
chunks: "initial", // 代碼分割類型
name: "vendor", // 代碼塊名稱
priority: 10, // 優(yōu)先級(jí)
test: /node_modules/ // 校驗(yàn)文件正則表達(dá)式
}
}, // 緩存組
chunks: "all" // 代碼分割類型:all全部模塊,async異步模塊,initial入口模塊
} // 代碼塊分割
}
};
??搖樹優(yōu)化
「刪除項(xiàng)目中未被引用代碼」,好處是移除重復(fù)代碼和未使用代碼。搖樹優(yōu)化首次出現(xiàn)于rollup,是rollup的核心概念,后來(lái)在webpack v2里借鑒過(guò)來(lái)使用。
搖樹優(yōu)化只對(duì)ESM規(guī)范生效,對(duì)其他模塊規(guī)范失效。搖樹優(yōu)化針對(duì)靜態(tài)結(jié)構(gòu)分析,只有import/export才能提供靜態(tài)的導(dǎo)入/導(dǎo)出功能。因此在編寫業(yè)務(wù)代碼時(shí)必須使用ESM規(guī)范才能讓搖樹優(yōu)化移除重復(fù)代碼和未使用代碼。
在webpack里只需將打包環(huán)境設(shè)置成生產(chǎn)環(huán)境就能讓搖樹優(yōu)化生效,同時(shí)業(yè)務(wù)代碼使用ESM規(guī)范編寫,使用import導(dǎo)入模塊,使用export導(dǎo)出模塊。
export default {
// ...
mode: "production"
};
??動(dòng)態(tài)墊片
「通過(guò)墊片服務(wù)根據(jù)UA返回當(dāng)前瀏覽器代碼墊片」,好處是無(wú)需將繁重的代碼墊片打包進(jìn)去。每次構(gòu)建都配置@babel/preset-env和core-js根據(jù)某些需求將Polyfill打包進(jìn)來(lái),這無(wú)疑又為代碼體積增加了貢獻(xiàn)。
@babel/preset-env提供的useBuiltIns可按需導(dǎo)入Polyfill。
-
「false」:無(wú)視 target.browsers將所有Polyfill加載進(jìn)來(lái) -
「entry」:根據(jù) target.browsers將部分Polyfill加載進(jìn)來(lái)(僅引入有瀏覽器不支持的Polyfill,需在入口文件import "core-js/stable") -
「usage」:根據(jù) target.browsers和檢測(cè)代碼里ES6的使用情況將部分Polyfill加載進(jìn)來(lái)(無(wú)需在入口文件import "core-js/stable")
在此推薦大家使用動(dòng)態(tài)墊片。動(dòng)態(tài)墊片可根據(jù)瀏覽器UserAgent返回當(dāng)前瀏覽器Polyfill,其思路是根據(jù)瀏覽器的UserAgent從browserlist查找出當(dāng)前瀏覽器哪些特性缺乏支持從而返回這些特性的Polyfill。對(duì)這方面感興趣的同學(xué)可參考polyfill-library和polyfill-service的源碼。
在此提供兩個(gè)動(dòng)態(tài)墊片服務(wù),可在不同瀏覽器里點(diǎn)擊以下鏈接看看輸出不同的Polyfill。相信IExplore還是最多Polyfill的,它自豪地說(shuō):我就是我,不一樣的煙火。
-
「官方CDN服務(wù)」:https://polyfill.io/v3/polyfill.min.js -
「阿里CDN服務(wù)」:https://polyfill.alicdn.com/polyfill.min.js
使用html-webpack-tags-plugin在打包時(shí)自動(dòng)插入動(dòng)態(tài)墊片。
import HtmlTagsPlugin from "html-webpack-tags-plugin";
export default {
plugins: [
new HtmlTagsPlugin({
append: false, // 在生成資源后插入
publicPath: false, // 使用公共路徑
tags: ["https://polyfill.alicdn.com/polyfill.min.js"] // 資源路徑
})
]
};
??按需加載
「將路由頁(yè)面/觸發(fā)性功能單獨(dú)打包為一個(gè)文件,使用時(shí)才加載」,好處是減輕首屏渲染的負(fù)擔(dān)。因?yàn)轫?xiàng)目功能越多其打包體積越大,導(dǎo)致首屏渲染速度越慢。
首屏渲染時(shí)只需對(duì)應(yīng)JS代碼而無(wú)需其他JS代碼,所以可使用按需加載。webpack v4提供模塊按需切割加載功能,配合import()可做到首屏渲染減包的效果,從而加快首屏渲染速度。只有當(dāng)觸發(fā)某些功能時(shí)才會(huì)加載當(dāng)前功能的JS代碼。
webpack v4提供魔術(shù)注解命名切割模塊,若無(wú)注解則切割出來(lái)的模塊無(wú)法分辨出屬于哪個(gè)業(yè)務(wù)模塊,所以一般都是一個(gè)業(yè)務(wù)模塊共用一個(gè)切割模塊的注解名稱。
const Login = () => import( /* webpackChunkName: "login" */ "../../views/login");
const Logon = () => import( /* webpackChunkName: "logon" */ "../../views/logon");
運(yùn)行起來(lái)控制臺(tái)可能會(huì)報(bào)錯(cuò),在package.json的babel相關(guān)配置里接入@babel/plugin-syntax-dynamic-import即可。
{
// ...
"babel": {
// ...
"plugins": [
// ...
"@babel/plugin-syntax-dynamic-import"
]
}
}
??作用提升
「分析模塊間依賴關(guān)系,把打包好的模塊合并到一個(gè)函數(shù)中」,好處是減少函數(shù)聲明和內(nèi)存花銷。作用提升首次出現(xiàn)于rollup,是rollup的核心概念,后來(lái)在webpack v3里借鑒過(guò)來(lái)使用。
在未開啟作用提升前,構(gòu)建后的代碼會(huì)存在大量函數(shù)閉包。由于模塊依賴,通過(guò)webpack打包后會(huì)轉(zhuǎn)換成IIFE,大量函數(shù)閉包包裹代碼會(huì)導(dǎo)致打包體積增大(模塊越多越明顯)。在運(yùn)行代碼時(shí)創(chuàng)建的函數(shù)作用域變多,從而導(dǎo)致更大的內(nèi)存開銷。
在開啟作用提升后,構(gòu)建后的代碼會(huì)按照引入順序放到一個(gè)函數(shù)作用域里,通過(guò)適當(dāng)重命名某些變量以防止變量名沖突,從而減少函數(shù)聲明和內(nèi)存花銷。
在webpack里只需將打包環(huán)境設(shè)置成生產(chǎn)環(huán)境就能讓作用提升生效,或顯式設(shè)置concatenateModules。
export default {
// ...
mode: "production"
};
// 顯式設(shè)置
export default {
// ...
optimization: {
// ...
concatenateModules: true
}
};
??壓縮資源
「壓縮HTML/CSS/JS代碼,壓縮字體/圖像/音頻/視頻」,好處是更有效減少打包體積。極致地優(yōu)化代碼都有可能不及優(yōu)化一個(gè)資源文件的體積更有效。
針對(duì)HTML代碼,使用html-webpack-plugin開啟壓縮功能。
import HtmlPlugin from "html-webpack-plugin";
export default {
// ...
plugins: [
// ...
HtmlPlugin({
// ...
minify: {
collapseWhitespace: true,
removeComments: true
} // 壓縮HTML
})
]
};
針對(duì)CSS/JS代碼,分別使用以下插件開啟壓縮功能。其中OptimizeCss基于cssnano封裝,Uglifyjs和Terser都是webpack官方插件,同時(shí)需注意壓縮JS代碼需區(qū)分ES5和ES6。
-
optimize-css-assets-webpack-plugin:壓縮 CSS代碼 -
uglifyjs-webpack-plugin:壓縮 ES5版本的JS代碼 -
terser-webpack-plugin:壓縮 ES6版本的JS代碼
import OptimizeCssAssetsPlugin from "optimize-css-assets-webpack-plugin";
import TerserPlugin from "terser-webpack-plugin";
import UglifyjsPlugin from "uglifyjs-webpack-plugin";
const compressOpts = type => ({
cache: true, // 緩存文件
parallel: true, // 并行處理
[`${type}Options`]: {
beautify: false,
compress: { drop_console: true }
} // 壓縮配置
});
const compressCss = new OptimizeCssAssetsPlugin({
cssProcessorOptions: {
autoprefixer: { remove: false }, // 設(shè)置autoprefixer保留過(guò)時(shí)樣式
safe: true // 避免cssnano重新計(jì)算z-index
}
});
const compressJs = USE_ES6
? new TerserPlugin(compressOpts("terser"))
: new UglifyjsPlugin(compressOpts("uglify"));
export default {
// ...
optimization: {
// ...
minimizer: [compressCss, compressJs] // 代碼壓縮
}
};
針對(duì)字體/音頻/視頻文件,還真沒相關(guān)Plugin供我們使用,就只能拜托大家在發(fā)布項(xiàng)目到生產(chǎn)服前使用對(duì)應(yīng)的壓縮工具處理了。針對(duì)圖像文件,大部分Loader/Plugin封裝時(shí)均使用了某些圖像處理工具,而這些工具的某些功能又托管在國(guó)外服務(wù)器里,所以導(dǎo)致經(jīng)常安裝失敗。具體解決方式可回看筆者曾經(jīng)發(fā)布的《聊聊NPM鏡像那些險(xiǎn)象環(huán)生的坑》一文尋求答案。
鑒于此,筆者花了一點(diǎn)小技巧開發(fā)了一個(gè)Plugin用于配合webpack壓縮圖像,詳情請(qǐng)參考tinyimg-webpack-plugin。
import TinyimgPlugin from "tinyimg-webpack-plugin";
export default {
// ...
plugins: [
// ...
TinyimgPlugin()
]
};
上述構(gòu)建策略都集成到筆者開源的bruce-cli里,它是一個(gè)「React/Vue」應(yīng)用自動(dòng)化構(gòu)建腳手架,其零配置開箱即用的優(yōu)點(diǎn)非常適合入門級(jí)、初中級(jí)、快速開發(fā)項(xiàng)目的前端同學(xué)使用,還可通過(guò)創(chuàng)建brucerc.js文件覆蓋其默認(rèn)配置,只需專注業(yè)務(wù)代碼的編寫無(wú)需關(guān)注構(gòu)建代碼的編寫,讓項(xiàng)目結(jié)構(gòu)更簡(jiǎn)潔。詳情請(qǐng)戳這里,使用時(shí)記得查看文檔,支持一個(gè)Star哈!
圖像策略
該策略主要圍繞圖像類型做相關(guān)處理,同時(shí)也是接入成本較低的性能優(yōu)化策略。只需做到以下兩點(diǎn)即可。
-
「圖像選型」:了解所有圖像類型的特點(diǎn)及其何種應(yīng)用場(chǎng)景最合適 -
「圖像壓縮」:在部署到生產(chǎn)環(huán)境前使用工具或腳本對(duì)其壓縮處理
圖像選型一定要知道每種圖像類型的體積/質(zhì)量/兼容/請(qǐng)求/壓縮/透明/場(chǎng)景等參數(shù)相對(duì)值,這樣才能迅速做出判斷在何種場(chǎng)景使用何種類型的圖像。
| 類型 | 體積 | 質(zhì)量 | 兼容 | 請(qǐng)求 | 壓縮 | 透明 | 場(chǎng)景 |
|---|---|---|---|---|---|---|---|
| JPG | 小 | 中 | 高 | 是 | 有損 | 不支持 | 背景圖、輪播圖、色彩豐富圖 |
| PNG | 大 | 高 | 高 | 是 | 無(wú)損 | 支持 | 圖標(biāo)、透明圖 |
| SVG | 小 | 高 | 高 | 是 | 無(wú)損 | 支持 | 圖標(biāo)、矢量圖 |
| WebP | 小 | 中 | 低 | 是 | 兼?zhèn)?/td> | 支持 | 看兼容情況 |
| Base64 | 看情況 | 中 | 高 | 否 | 無(wú)損 | 支持 | 圖標(biāo) |
圖像壓縮可在上述構(gòu)建策略-壓縮資源里完成,也可自行使用工具完成。由于現(xiàn)在大部分webpack圖像壓縮工具不是安裝失敗就是各種環(huán)境問(wèn)題(你懂的),所以筆者還是推薦在發(fā)布項(xiàng)目到生產(chǎn)服前使用圖像壓縮工具處理,這樣運(yùn)行穩(wěn)定也不會(huì)增加打包時(shí)間。
好用的圖像壓縮工具無(wú)非就是以下幾個(gè),若有更好用的工具麻煩在評(píng)論里補(bǔ)充喔!
| 工具 | 開源 | 收費(fèi) | API | 免費(fèi)體驗(yàn) |
|---|---|---|---|---|
| QuickPicture | ?? | ?? | ?? | 可壓縮類型較多,壓縮質(zhì)感較好,有體積限制,有數(shù)量限制 |
| ShrinkMe | ?? | ?? | ?? | 可壓縮類型較多,壓縮質(zhì)感一般,無(wú)數(shù)量限制,有體積限制 |
| Squoosh | ?? | ?? | ?? | 可壓縮類型較少,壓縮質(zhì)感一般,無(wú)數(shù)量限制,有體積限制 |
| TinyJpg | ?? | ?? | ?? | 可壓縮類型較少,壓縮質(zhì)感很好,有數(shù)量限制,有體積限制 |
| TinyPng | ?? | ?? | ?? | 可壓縮類型較少,壓縮質(zhì)感很好,有數(shù)量限制,有體積限制 |
| Zhitu | ?? | ?? | ?? | 可壓縮類型一般,壓縮質(zhì)感一般,有數(shù)量限制,有體積限制 |
若不想在網(wǎng)站里來(lái)回拖動(dòng)圖像文件,可使用筆者開源的圖像批處理工具img-master代替,不僅有壓縮功能,還有分組功能、標(biāo)記功能和變換功能。目前筆者負(fù)責(zé)的全部項(xiàng)目都使用該工具處理,一直用一直爽!
圖像策略也許處理一張圖像就能完爆所有構(gòu)建策略,因此是一種很廉價(jià)但極有效的性能優(yōu)化策略。
分發(fā)策略
該策略主要圍繞內(nèi)容分發(fā)網(wǎng)絡(luò)做相關(guān)處理,同時(shí)也是接入成本較高的性能優(yōu)化策略,需足夠資金支持。
雖然接入成本較高,但大部分企業(yè)都會(huì)購(gòu)買一些CDN服務(wù)器,所以在部署的事情上就不用過(guò)分擔(dān)憂,盡管使用就好。該策略盡量遵循以下兩點(diǎn)就能發(fā)揮CDN最大作用。
-
「所有靜態(tài)資源走CDN」:開發(fā)階段確定哪些文件屬于靜態(tài)資源 -
「把靜態(tài)資源與主頁(yè)面置于不同域名下」:避免請(qǐng)求帶上 Cookie
「內(nèi)容分發(fā)網(wǎng)絡(luò)」簡(jiǎn)稱「CDN」,指一組分布在各地存儲(chǔ)數(shù)據(jù)副本并可根據(jù)就近原則滿足數(shù)據(jù)請(qǐng)求的服務(wù)器。其核心特征是緩存和回源,緩存是把資源復(fù)制到CDN服務(wù)器里,回源是資源過(guò)期/不存在就向上層服務(wù)器請(qǐng)求并復(fù)制到CDN服務(wù)器里。
使用CDN可降低網(wǎng)絡(luò)擁塞,提高用戶訪問(wèn)響應(yīng)速度和命中率。構(gòu)建在現(xiàn)有網(wǎng)絡(luò)基礎(chǔ)上的智能虛擬網(wǎng)絡(luò),依靠部署在各地服務(wù)器,通過(guò)中心平臺(tái)的調(diào)度、負(fù)載均衡、內(nèi)容分發(fā)等功能模塊,使用戶就近獲取所需資源,這就是CDN的終極使命。
基于CDN的「就近原則」所帶來(lái)的優(yōu)點(diǎn),可將網(wǎng)站所有靜態(tài)資源全部部署到CDN服務(wù)器里。那靜態(tài)資源包括哪些文件?通常來(lái)說(shuō)就是無(wú)需服務(wù)器產(chǎn)生計(jì)算就能得到的資源,例如不常變化的樣式文件、腳本文件和多媒體文件(字體/圖像/音頻/視頻)等。
若需單獨(dú)配置CDN服務(wù)器,可考慮阿里云OSS、網(wǎng)易樹帆NOS和七牛云Kodo,當(dāng)然配置起來(lái)還需購(gòu)買該產(chǎn)品對(duì)應(yīng)的CDN服務(wù)。由于篇幅問(wèn)題,這些配置在購(gòu)買后會(huì)有相關(guān)教程,可自行體會(huì),在此就不再敘述了。
筆者推薦大家首選網(wǎng)易樹帆NOS,畢竟對(duì)自家產(chǎn)品還是挺有信心的,不小心給自家產(chǎn)品打了個(gè)小廣告了,哈哈!
緩存策略
該策略主要圍繞瀏覽器緩存做相關(guān)處理,同時(shí)也使接入成本最低的性能優(yōu)化策略。其顯著減少網(wǎng)絡(luò)傳輸所帶來(lái)的損耗,提升網(wǎng)頁(yè)訪問(wèn)速度,是一種很值得使用的性能優(yōu)化策略。
通過(guò)下圖可知,為了讓瀏覽器緩存發(fā)揮最大作用,該策略盡量遵循以下五點(diǎn)就能發(fā)揮瀏覽器緩存最大作用。
-
「考慮拒絕一切緩存策略」: Cache-Control:no-store -
「考慮資源是否每次向服務(wù)器請(qǐng)求」: Cache-Control:no-cache -
「考慮資源是否被代理服務(wù)器緩存」: Cache-Control:public/private -
「考慮資源過(guò)期時(shí)間」: Expires:t/Cache-Control:max-age=t,s-maxage=t -
「考慮協(xié)商緩存」: Last-Modified/Etag
同時(shí)瀏覽器緩存也是高頻面試題之一,筆者覺得上述涉及到的名詞在不同語(yǔ)序串聯(lián)下也能完全理解才能真正弄懂瀏覽器緩存在性能優(yōu)化里起到的作用。
緩存策略通過(guò)設(shè)置HTTP報(bào)文實(shí)現(xiàn),在形式上分為「強(qiáng)緩存/強(qiáng)制緩存」和「協(xié)商緩存/對(duì)比緩存」。為了方便對(duì)比,筆者將某些細(xì)節(jié)使用圖例展示,相信你有更好的理解。
整個(gè)緩存策略機(jī)制很明了,先走強(qiáng)緩存,若命中失敗才走協(xié)商緩存。若命中強(qiáng)緩存,直接使用強(qiáng)緩存;若未命中強(qiáng)緩存,發(fā)送請(qǐng)求到服務(wù)器檢查是否命中協(xié)商緩存;若命中協(xié)商緩存,服務(wù)器返回304通知瀏覽器使用本地緩存,否則返回最新資源。
有兩種較常用的應(yīng)用場(chǎng)景值得使用緩存策略一試,當(dāng)然更多應(yīng)用場(chǎng)景都可根據(jù)項(xiàng)目需求制定。
-
「頻繁變動(dòng)資源」:設(shè)置 Cache-Control:no-cache,使瀏覽器每次都發(fā)送請(qǐng)求到服務(wù)器,配合Last-Modified/ETag驗(yàn)證資源是否有效 -
「不常變化資源」:設(shè)置 Cache-Control:max-age=31536000,對(duì)文件名哈希處理,當(dāng)代碼修改后生成新的文件名,當(dāng)HTML文件引入文件名發(fā)生改變才會(huì)下載最新文件
渲染層面
「渲染層面」的性能優(yōu)化,無(wú)疑是如何讓代碼解析更好執(zhí)行更快。因此筆者從以下五方面做出建議。
-
「CSS策略」:基于CSS規(guī)則 -
「DOM策略」:基于DOM操作 -
「阻塞策略」:基于腳本加載 -
「回流重繪策略」:基于回流重繪 -
「異步更新策略」:基于異步更新
上述五方面都是編寫代碼時(shí)完成,充滿在整個(gè)項(xiàng)目流程的開發(fā)階段里。因此在開發(fā)階段需時(shí)刻注意以下涉及到的每一點(diǎn),養(yǎng)成良好的開發(fā)習(xí)慣,性能優(yōu)化也自然而然被使用上了。
渲染層面的性能優(yōu)化更多表現(xiàn)在編碼細(xì)節(jié)上,而并非實(shí)體代碼。簡(jiǎn)單來(lái)說(shuō)就是遵循某些編碼規(guī)則,才能將渲染層面的性能優(yōu)化發(fā)揮到最大作用。
「回流重繪策略」在渲染層面的性能優(yōu)化里占比較重,也是最常規(guī)的性能優(yōu)化之一。上年筆者發(fā)布的掘金小冊(cè)《玩轉(zhuǎn)CSS的藝術(shù)之美》使用一整章講解回流重繪,本章已開通試讀,更多細(xì)節(jié)請(qǐng)戳這里。
CSS策略
-
避免出現(xiàn)超過(guò)三層的 嵌套規(guī)則 -
避免為 ID選擇器添加多余選擇器 -
避免使用 標(biāo)簽選擇器代替類選擇器 -
避免使用 通配選擇器,只對(duì)目標(biāo)節(jié)點(diǎn)聲明規(guī)則 -
避免重復(fù)匹配重復(fù)定義,關(guān)注 可繼承屬性
DOM策略
-
緩存 DOM計(jì)算屬性 -
避免過(guò)多 DOM操作 -
使用 DOMFragment緩存批量化DOM操作
阻塞策略
-
腳本與 DOM/其它腳本的依賴關(guān)系很強(qiáng):對(duì)<script>設(shè)置defer -
腳本與 DOM/其它腳本的依賴關(guān)系不強(qiáng):對(duì)<script>設(shè)置async
回流重繪策略
-
緩存 DOM計(jì)算屬性 -
使用類合并樣式,避免逐條改變樣式 -
使用 display控制DOM顯隱,將DOM離線化
異步更新策略
-
在 異步任務(wù)中修改DOM時(shí)把其包裝成微任務(wù)
六大指標(biāo)
筆者根據(jù)性能優(yōu)化的重要性和實(shí)際性劃分出九大策略和六大指標(biāo),其實(shí)它們都是一條條活生生的性能優(yōu)化建議。有些性能優(yōu)化建議接不接入影響都不大,因此筆者將九大策略定位高于六大指標(biāo)。針對(duì)九大策略還是建議在開發(fā)階段和生產(chǎn)階段接入,在項(xiàng)目復(fù)盤時(shí)可將六大指標(biāo)的條條框框根據(jù)實(shí)際應(yīng)用場(chǎng)景接入。
六大指標(biāo)基本囊括大部分性能優(yōu)化細(xì)節(jié),可作為九大策略的補(bǔ)充。筆者根據(jù)每條性能優(yōu)化建議的特征將指標(biāo)劃分為以下六方面。
-
「加載優(yōu)化」:資源在加載時(shí)可做的性能優(yōu)化 -
「執(zhí)行優(yōu)化」:資源在執(zhí)行時(shí)可做的性能優(yōu)化 -
「渲染優(yōu)化」:資源在渲染時(shí)可做的性能優(yōu)化 -
「樣式優(yōu)化」:樣式在編碼時(shí)可做的性能優(yōu)化 -
「腳本優(yōu)化」:腳本在編碼時(shí)可做的性能優(yōu)化 -
「V8引擎優(yōu)化」:針對(duì) V8引擎特征可做的性能優(yōu)化
加載優(yōu)化
執(zhí)行優(yōu)化
渲染優(yōu)化
樣式優(yōu)化
腳本優(yōu)化
V8引擎優(yōu)化
總結(jié)
「性能優(yōu)化」作為老生常談的知識(shí),必然會(huì)在工作或面試時(shí)遇上。很多時(shí)候不是想到某條性能優(yōu)化建議就去做或答,而是要對(duì)這方面有一個(gè)整體認(rèn)知,知道為何這樣設(shè)計(jì),這樣設(shè)計(jì)的目的能達(dá)到什么效果。
性能優(yōu)化不是通過(guò)一篇文章就能全部講完,若詳細(xì)去講可能要寫兩本書的篇幅才能講完。本文能到給大家的就是一個(gè)方向一種態(tài)度,學(xué)以致用唄,希望閱讀完本文會(huì)對(duì)你有所幫助。
最后,筆者將本文所有內(nèi)容整理成一張高清腦圖,由于體積太大無(wú)法上傳,可關(guān)注筆者個(gè)人公眾號(hào)「IQ前端」并回復(fù)性能優(yōu)化獲取口袋知識(shí)圖譜吧!
往期超過(guò)5萬(wàn)閱讀量的掘金爆文
-
靈活運(yùn)用CSS開發(fā)技巧: 4500+點(diǎn)贊量,13.8w閱讀量 -
靈活運(yùn)用JS開發(fā)技巧: 1700+點(diǎn)贊量,5.4w閱讀量 -
1.5萬(wàn)字概括ES6全部特性: 4500+點(diǎn)贊量,16.5w閱讀量 -
中高級(jí)前端必須注意的40條移動(dòng)端H5坑位指南|網(wǎng)易三年實(shí)踐: 3800+點(diǎn)贊量,5.7w閱讀量
如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。
-
