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