前端極致性能優化手冊大全
前端優化之路必不可少的知識點。
- 瀏覽器輸入url到頁面的展現,具體發生了些什麼,前端能做哪些優化
- DNS 遞歸查詢解析 —— DNS優化prefetch;
- TCP 三次握手、四次揮手 —— http1.0/1.1/2.0 的區別,http/s的區別;
- http 緩存 —— 304 與 CDN;
- 瀏覽器渲染機制 —— CSS、JS順序的重要性,@import的損耗,防抖、節流、重排、重繪,GPU加速等;
- 如何優化JS主線程 —— web worker,時間分片
- …
- 圖片你優化了嗎,雪碧圖、webp、svg;
- webpack 等打包優化
- 運維的基本知識 nginx
本文按一定順序總結與前端性能優化的基本點,大家可以按步驟逐一檢查自己的項目,找出性能的瓶頸。如有錯誤遺漏歡迎補充糾正。
文章有些原理細節都在參考文章中,價值較高建議讀一讀。
webpack
默認的 webpack4
很多優化內部已經做到很好了,但無法滿足所有的業務場景,
如果發現開發時打包慢、打包體積太大,這是你就要審視下配置了。
代碼分塊分析插件 webpack-bundle-analyzer
npm i webpack-bundle-analyzer -D
- 修改
webpack.config.js
// 在頭部添加
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
// 在plugins: [] 中新增配置如下
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8000,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info'
})
- 啓動本地開發服務,瀏覽器中打開
http://127.0.0.1:8000
- webpack4 默認代碼分割策略
- 新的 chunk 是否被共享或者是來自 node_modules 的模塊
- 新的 chunk 體積在壓縮之前是否大於 30kb
- 按需加載 chunk 的併發請求數量小於等於 5 個
- 頁面初始加載時的併發請求數量小於等於 3 個
比如,由於業務中頻繁 antd
中的UI組件,但他們都小於 30kb
不會被獨立打包,導致過多重複的代碼打入不同 chunk
中。
這時根據實際業務情況,默認的配置就不滿足需求了。修改策略:
- react 全家桶和狀態管理一個
vendor
包 - antd 相關的一個
lib
包 - node_modules 裏的打成
common
包
// 默認配置
splitChunks: {
chunks: 'all',
name: false,
}
// 修改後的配置
splitChunks: {
chunks: 'all',
name: false,
cacheGroups: {
vendor: {
name: 'vendor',
test: module = >/(react|react-dom|react-router-dom|mobx|mobx-react)/.test(module.context),
chunks: 'initial',
priority: 11
},
libs: {
name: 'libs',
test: (module) = >/(moment|antd|lodash)/.test(module.context),
chunks: 'all',
priority: 14
},
common: {
chunks: "async",
test: /[\\/] node_modules[\\ / ] / ,
name: "common",
minChunks: 3,
maxAsyncRequests: 5,
maxSize: 1000000,
priority: 10
}
}
}
結論:
- 優化前體積爲56MB(去除sourceMap)
- 優化後體積爲36MB(保留sourceMap,去除sourceMap大約在7.625MB)
glob 和 purgecss-webpack-plugin 去除無用CSS
npm i glob purgecss-webpack-plugin -D
// 在webpack.config.js中的plugins: []中添加.
// 需要注意的是paths一定要是絕對路徑,比如antd的樣式不在src目錄下就需要寫成一個數組,要不然antd的樣式就會不見了
new purgecssWebpackPlugin({
paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true })
})
結論:CSS
資源減小很多
一番操作下來
- 目前資源已經減小到7.25M左右;
- 打包速度由7.5分鐘減少到2.5分鐘,效率極大提升。
圖片
webp
webp
是一種新式圖片格式,在保證品質的同時提供無損和有損壓縮。
webp
對於圖片較多的站點是必不可少的優化手段,一般 CDN
都有提供轉換服務。
- 某寶大規模使用
- 優點:在同等品質下,無損圖片比
png
減少26%
的大小,有損下比jpeg
小25-34%
。 - 缺點:有的瀏覽器不兼容,需要做兼容,以下是官方提供
// check_webp_feature:
// 'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
// 'callback(feature, result)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}
雪碧圖
多個圖片拼成一個圖片,在利用 background-position
定位。
減少 http 請求。
HTTP2 可以解決線頭阻塞問題。
iconfont
svg 版雪碧圖
base64
// 字符轉
window.btoa('str');
// canvas 轉
canvas.toDataURL();
// 圖片轉
const reader = new FileReader();
const imgUrlBase64 = reader.readAsDataURL(file);
// webpack 打包轉
// url-loader
- 優點:便於存儲在html、js中,減少http請求個數。
- 缺點:文件尺寸增大
30%
左右。
適用於少量小圖的場景。
緩存
DNS緩存
查找過程
- 先瀏覽器 DNS 緩存
- 查找
hosts
文件域名IP
映射(你知道背牆DNS污染但沒封的IP,可以設置hosts文件訪問) - 查找本地
DNS解析器
(路由) 緩存 - 根DNS服務器 -> 頂級.com -> 二級域名服務器xx.com -> 主機 www.xx.com
- 可以看出某些
DNS
解析佔大頭時間,優化還是很有必要的
dns-prefetch
,例如訪問某寶首頁,猜測你接下來要訪問某些域名,提前去解析。以節省下個頁面的DNS
查詢。
不過大量不必要的預獲取,對公共網絡資源造成較大浪費。
- 優化後
http 緩存
簡單提下,對於現在 SPA
項目,一般靜態資源放在 CDN 上,
對經常變動入口文件index.html
設置強制檢驗過期 Cache-Control: no-cahce
或直接不緩存。
其他 hash
命名的資源直接設置長緩存(max-age: 一年半載)。
具體詳情已在另一篇文字闡釋,文末鏈接。
CDN(Content Delivery Network) 內容分發網絡
優點:
- 資源文件多處備份,就近原則,網絡離用戶最近的服務器提供服務,速度快、安全性高;
- 帶寬貴啊,大量的用戶訪問,不上
CDN
,網站很卡或崩潰。
本地緩存 localStorage、sessionStorage、cookie
- storage
- localStorage 一直存在瀏覽器中,要麼用戶刪除或瀏覽器緩存策略剔除
- sessionStorage 頁面關閉消失
優點:可以存儲較大的數據 Chrome 5M
- cookie
相比 storage
:
- 優點:可以設置失效時間
- 缺點:存儲量較小,
http1.x
每次會上報給服務器,造成網絡浪費。
建議:對服務器安全數據設置 http-only
,能少用盡量少用,只用來與服務器進行狀態維護和用戶識別。
瀏覽器渲染
CSS
- 減少
@import
的使用,瀏覽器解析html
會優化嗅探獲併發獲取文件,如果使用@import
需要下載解析了當前CSS
文件,才能下載。 - CSS 權重較高,應該優先下載解析。
腳本 defer、async
只對外聯腳本有效。衆所周知,腳本解析會阻塞 DOM
解析,這兩個屬性就是爲了解決這個問題。
-
defer
下載時不阻塞 HTML 解析成 DOM,下載完成會等待DOM
構建完畢且在DOMContentLoaded
事件觸發之前執行,多個defer
腳本保證腳本執行順序。 -
async
下載時不阻塞 HTML 解析成 DOM,下載完畢後儘量安排JS執行。意思說執行時間不確定,早下載早執行。如果DOM
未構建完,腳本可能會阻塞DOM構建。 -
例如某寶大量在頭部使用
async
- 但不是很理解是,按原理
async
應該用在對DOM構建
和腳本順序無依賴的場景,而且下載太快還可能阻塞DOM構建
。感覺defer
更合適。
防抖、節流
在其他文章有詳細說明,在此不再贅述,請看參考索引。
防止強制佈局
- 主要是在避免在循環中又讀又寫樣式
GPU 加速
will-change: transform
transform: translateZ(0)
會單獨把元素提升層級交給 GPU
渲染,適合一些 Animation
動畫。
requestAnimation, requestIdelCallback
google
文檔上有很多探討,檢測計算長任務的新api
進展。Facebook
最新react
中的fiber
調度,就使用了requestAnimation
,requestIdelCallback
來
進行長任務的時間切片,避免以前深DOM
樹更新產生長耗時甚至抖動。
web worker
對於需要大量計算會佔用渲染主線程,適合放到 web worker
來執行。
服務器
http2
http1.1 對比 http 1.0 主要進步有
- 緩存處理的增強,如
Etag
- 加入
range
頭,響應碼206(Partial Content)
支持斷點續傳 - 加入
host
頭,多個域名可以綁定一個IP
- 響應頭
Connection: keep-alive
,長連接,客戶端與同一個主機通信不必多次三次握手
https 與 http
- https 需要申請
CA
證書,要錢;https 在 http 上多了層安全協議SSL/TLS
; - 客戶端進行
CA
認證,連接需要非對稱加密(耗時),傳輸數據使用對稱加密; - 防止運營商 http 劫持,插廣告等。http 端口
80
,https443
。
http2 對比 http1.x
- header壓縮,例如後者每次傳輸數據都得帶很多相同的頭部信息,http2 會壓縮頭部且避免重複頭部重傳;
- 新的二進制格式,後者是基於文本協議解析,前者基於01串,方便且健壯;
- 多路複用,注意與
keep-alive
區分。前者是連接共享,每個請求有個唯一id
來確認歸屬,多個請求可以同時相互混雜。
而後者減少了握手保持長聯,會影響服務器性能,先進先出需要等前一個請求發完才能進行下一個,造成線頭阻塞。客戶端一般會限制一個頁面與不同服務器同時http連接上限; - 服務器推送,http1.x服務器只能被動接收請求發送資源,HTTP2可以主動推送。
http2 可以提升傳輸效率。nginx 有必要做好 http2 的升級和降級處理。
gzip
服務器開啓壓縮,文本類型的文件能夠減少網絡傳輸。
特別是文件較大且重複率高的文本壓縮效果更明顯。
如圖 index.html
文件壓縮 (383-230)/383=39.95%
。
- Chrome request headers 告訴服務器支持的壓縮算法:
Accept-Encoding: gzip, deflate, br
- 服務器響應使用的壓縮算法 response headers:
Content-Encoding: gzip
- nginx 開啓
gzip on;
// 不壓縮臨界值,大於1K的才壓縮,一般不用改
gzip_min_length 1k;
// 壓縮級別,1-10,數字越大壓縮的越細,時間也越長
gzip_comp_level 2;
// 進行壓縮的文件類型
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
// ie兼容性不好所以放棄
gzip_disable "MSIE [1-6]\.";
compression-webpack-plugin 配合 gzip
npm i compression-webpack-plugin -D
const CompressionWebpackPlugin = require('compression-webpack-plugin');
// 在webpack.config.js中的plugins: []中添加.
new CompressionWebpackPlugin({
asset: '[path].gz[query]', // 目標文件名
algorithm: 'gzip', // 使用gzip壓縮
test: /\.(js|css)$/, // 壓縮 js 與 css
threshold: 10240, // 資源文件大於10240B=10kB時會被壓縮
minRatio: 0.8 // 最小壓縮比達到0.8時纔會被壓縮
})
- 優點
nginx
開啓gzip
,發現有壓縮好的gzip
壓縮文件,就會直接使用,減少服務器cpu
的資源的使用。 - 缺點
打包時間就會拉長。現在靜態資源一般會上CDN
,gzip
是CDN服務器基本的服務,
需要去節省本就花了錢買的服務器資源,而增加打包的時間嗎?
es6、動態使用POLYFILL
webpack
默認支持 es6
的新特性 tree-shaking
,可以搖掉不用的代碼,且新api的性能很高。
推薦全面使用。
// 會根據ua返回不同的內容
https://polyfill.io/v3/polyfill.min.js
方案 | 優點 | 缺點 |
---|---|---|
babel-polyfill | React 官方推薦 | 體積200kb |
babel-plugin-transform-runtime | 體積小 | 不能poyfill原型上的方法 |
polyfill-service | 動態根據ui加載 | 兼容性,國內部分瀏覽器有問題 |
結論:可以減少資源大小,但依賴外部服務,自建麻煩的話,放棄。