拯救你的年底 KPI:前端性能優(yōu)化
(給全棧前端精選加星標,提升前端技能)
原文地址:https://juejin.cn/post/6911472693405548557
作者:販賣焦慮
??前言
性能優(yōu)化 ,每個工程師跑不掉的一個話題。這里是本人總結的一些優(yōu)化手法,希望對大家有所幫助,感謝各位大哥??
演示demo
在線預覽: http://118.25.49.69:8085/
fe-optimize 源碼: https://github.com/alexwjj/fe-optimize

演示PPT
在線預覽:http://118.25.49.69:8086/
fe-ppt 使用HTML做的PPT源碼

前端性能的影響(長篇大論警告??????)
前端性能的一個重要指標是頁面加載時間,不僅事關用戶體驗,也是搜索引擎排名考慮的一個因素。
來自Google的數(shù)據(jù)表明,一個有10條數(shù)據(jù)0.4秒能加載完的頁面,變成30條數(shù)據(jù)0.9秒加載完之后,流量和廣告收入下降90%。 Google Map 首頁文件大小從100KB減小到70-80KB后,流量在第一周漲了10%,接下來的三周漲了25%。 亞馬遜的數(shù)據(jù)表明:加載時間增加100毫秒,銷量就下降1%。所以:重鑄性能之光,我輩義不容辭??
一、調(diào)試工具
磨刀不誤砍柴工,讀完大學再打工!


1、Network

這里可以看到資源加載詳情,初步評估影響頁面性能的因素。鼠標右鍵可以自定義選項卡,頁面底部是當前加載資源的一個概覽。DOMContentLoaded DOM渲染完成的時間,Load:當前頁面所有資源加載完成的時間
#思考:如何判斷哪些資源對當前頁面加載無用,做對應優(yōu)化?shift + cmd + P 調(diào)出控制臺的擴展工具,添加規(guī)則
擴展工具 更多使用姿勢
#瀑布流waterfall

Queueing 瀏覽器將資源放入隊列時間 Stalled 因放入隊列時間而發(fā)生的停滯時間 DNS Lookup DNS解析時間 Initial connection 建立HTTP連接的時間 SSL 瀏覽器與服務器建立安全性連接的時間 TTFB 等待服務端返回數(shù)據(jù)的時間 Content Download 瀏覽器下載資源的時間
2、Lighthouse

First Contentful Paint 首屏渲染時間,1s以內(nèi)綠色 Speed Index 速度指數(shù),4s以內(nèi)綠色 Time to Interactive 到頁面可交換的時間
根據(jù)chrome的一些策略自動對網(wǎng)站做一個質(zhì)量評估,并且會給出一些優(yōu)化的建議。
3、Peformance

對網(wǎng)站最專業(yè)的分析~后面會多次講到
4、webPageTest
可以模擬不同場景下訪問的情況,比如模擬不同瀏覽器、不同國家等等,在線測試地址:webPageTest


5、資源打包分析
#webpack-bundle-analyzer

npm?install?--save-dev?webpack-bundle-analyzer
//?webpack.config.js?文件
const?BundleAnalyzerPlugin?=?require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
??plugins:?[
????new?BundleAnalyzerPlugin({
??????????analyzerMode:?'server',
??????????analyzerHost:?'127.0.0.1',
??????????analyzerPort:?8889,
??????????reportFilename:?'report.html',
??????????defaultSizes:?'parsed',
??????????openAnalyzer:?true,
??????????generateStatsFile:?false,
??????????statsFilename:?'stats.json',
??????????statsOptions:?null,
??????????logLevel:?'info'
????????}),
??]
}
//?package.json
"analyz":?"NODE_ENV=production?npm_config_report=true?npm?run?build"
#開啟source-mapwebpack.config.js
module.exports?=?{
????mode:?'production',
????devtool:?'hidden-source-map',
}
package.json
"analyze":?"source-map-explorer?'build/*.js'",
npm?run?analyze?
二、WEB API
工欲善其事,必先利其器。瀏覽器提供的一些分析API至關重要
1、監(jiān)聽視窗激活狀態(tài)
大學都刷過慕課吧?只要離開窗口視頻就會暫停~
或者一些考試網(wǎng)站,提醒你不能離開當前窗口
再或者,這種效果~

//?窗口激活狀態(tài)監(jiān)聽
let?vEvent?=?'visibilitychange';
if?(document.webkitHidden?!=?undefined)?{
????vEvent?=?'webkitvisibilitychange';
}
function?visibilityChanged()?{
????if?(document.hidden?||?document.webkitHidden)?{
????????document.title?=?'客官,別走啊~'
????????console.log("Web?page?is?hidden.")
????}?else?{
????????document.title?=?'客官,你又回來了呢~'
????????console.log("Web?page?is?visible.")
????}
}
document.addEventListener(vEvent,?visibilityChanged,?false);
其實有很多隱藏的api,這里大家有興趣的可以去試試看:
2、觀察長任務(performance 中Task)
const?observer?=?new?PerformanceObserver((list)?=>?{
????for?(const?entry?of?list.getEntries())?{
????????console.log(entry)
????}
})
observer.observe({entryTypes:?['longtask']})
3、監(jiān)聽網(wǎng)絡變化
網(wǎng)絡變化時給用戶反饋網(wǎng)絡問題,有時候看直播的時候自己的網(wǎng)絡卡頓,直播平臺也會提醒你或者自動給你切換清晰度
var?connection?=?navigator.connection?||?navigator.mozConnection?||?navigator.webkitConnection;
var?type?=?connection.effectiveType;
function?updateConnectionStatus()?{
??console.log("Connection?type?changed?from?"?+?type?+?"?to?"?+?connection.effectiveType);
??type?=?connection.effectiveType;
}
connection.addEventListener('change',?updateConnectionStatus);
4、計算DOMContentLoaded時間
window.addEventListener('DOMContentLoaded',?(event)?=>?{
????let?timing?=?performance.getEntriesByType('navigation')[0];
????console.log(timing.domInteractive);
????console.log(timing.fetchStart);
????let?diff?=?timing.domInteractive?-?timing.fetchStart;
????console.log("TTI:?"?+?diff);
})
5、更多計算規(guī)則
DNS?解析耗時:?domainLookupEnd?-?domainLookupStart
TCP?連接耗時:?connectEnd?-?connectStart
SSL?安全連接耗時:?connectEnd?-?secureConnectionStart
網(wǎng)絡請求耗時?(TTFB):?responseStart?-?requestStart
數(shù)據(jù)傳輸耗時:?responseEnd?-?responseStart
DOM?解析耗時:?domInteractive?-?responseEnd
資源加載耗時:?loadEventStart?-?domContentLoadedEventEnd
First?Byte時間:?responseStart?-?domainLookupStart
白屏時間:?responseEnd?-?fetchStart
首次可交互時間:?domInteractive?-?fetchStart
DOM?Ready?時間:?domContentLoadEventEnd?-?fetchStart
頁面完全加載時間:?loadEventStart?-?fetchStart
http 頭部大?。簍ransferSize - encodedBodySize
重定向次數(shù):performance.navigation.redirectCount
重定向耗時:?redirectEnd?-?redirectStart
三、老生常談,雅虎軍規(guī)
磨好刀了,就該想想往哪里捅比較好了~ ??????
關于雅虎軍規(guī),你知道的有多少條,平時寫用到的又有哪些?針對以下規(guī)則,我們可以做很多優(yōu)化工作

1、減少cookie傳輸
cookie傳輸會造成帶寬浪費,可以:
減少cookie中存儲的東西 靜態(tài)資源不需要cookie,可以采用其他的域名,不會主動帶上cookie。
2、避免過多的回流與重繪
連續(xù)觸發(fā)頁面回流操作
??let?cards?=?document.getElementsByClassName("MuiPaper-rounded");
??const?update?=?(timestamp)?=>?{
????for?(let?i?=?0;?i???????let?top?=?cards[i].offsetTop;
??????cards[i].style.width?=?((Math.sin(cards[i].offsetTop?+?timestamp?/?100?+?1)?*?500)?+?'px')
????}
????window.requestAnimationFrame(update)
??}
??update(1000);
看下效果,很明顯的卡頓

performance分析結果,load事件之后存在大量的回流,并且chrome都給標記了紅色

使用fastDom進行優(yōu)化,將對dom的讀和寫分離,合并
?let?cards?=?document.getElementsByClassName("MuiPaper-rounded");
??const?update?=?(timestamp)?=>?{
????for?(let?i?=?0;?i???????fastdom.measure(()?=>?{
????????let?top?=?cards[i].offsetTop;
????????fastdom.mutate(()?=>?{
??????????cards[i].style.width?=
????????????Math.sin(top?+?timestamp?/?100?+?1)?*?500?+?"px";
????????});
??????});
????}
????window.requestAnimationFrame(update)
??}
??update(1000);
再看下效果,很流暢~ ?

performance分析結果,load事件之后也沒有了那么多的紅色標記

感興趣的可以去了解一下fastDom:github fastdom 在線預覽:fastdom demo
關于任務拆分與組合的思想,react fiber架構做的很牛逼,有興趣的可以去了解一下調(diào)度算法在fiber中的實踐
四、壓縮

嗯哼哼、確定一下沒有走錯場子,繼續(xù)繼續(xù)!
1、Gzip
開啟方式可參考:nginx開啟gzip

還有一種方式:打包的時候生成gz文件,上傳到服務器端,這樣就不需要nginx來壓縮了,可以降低服務器壓力??蓞⒖迹篻zip壓縮文件&webPack配置Compression-webpack-plugin
2、服務端壓縮
server.js
const?express?=?require('express');
const?app?=?express();
const?fs?=?require('fs');
const?compression?=?require('compression');
const?path?=?require('path');
app.use(compression());
app.use(express.static('build'));
app.get('*',?(req,res)?=>{
????res.sendFile(path.join(__dirname+'/build/index.html'));
});
const?listener?=?app.listen(process.env.PORT?||?3000,?function?()?{
????console.log(`Listening?on?port?${listener.address().port}`);
});
package.json
"start":?"npm?run?build?&&?node?server.js",
3、JavaScript、Css、Html壓縮
工程化項目中直接使用對應的插件即可,webpack的主要有下面三個:
UglifyJS webpack-parallel-uglify-plugin terser-webpack-plugin 具體優(yōu)缺點可參考:webpack常用的三種JS壓縮插件。壓縮原理簡單的講就是去除一些空格、換行、注釋,借助es6模塊化的功能,做了一些tree-shaking的優(yōu)化。同時做了一些代碼混淆,一方面是為了更小的體積,另一方面也是為了源碼的安全性。css壓縮主要是mini-css-extract-plugin,當然前面的js壓縮插件也會給你做好css壓縮。使用姿勢:
npm?install?--save-dev?mini-css-extract-plugin
const?MiniCssExtractPlugin?=?require("mini-css-extract-plugin");
plugins:[
?new?MiniCssExtractPlugin({
???????filename:?"[name].css",
???????chunkFilename:?"[id].css"
???})
]
html壓縮可以用HtmlWebpackPlugin,單頁項目就一個index.html,性能提升微乎其微~
4、http2首部壓縮
http2的特點
二進制分幀 首部壓縮 流量控制 多路復用 請求優(yōu)先級 服務器推送http2_push: 'xxx.jpg' 具體升級方式也很簡單,修改一下nginx配置,方法請自行Google
五、webpack優(yōu)化
上文中也提到了部分webpack插件,下面我再來看看還有哪些~
1、DllPlugin 提升構建速度
通過DllPlugin插件,將一些比較大的,基本很少升級的包拆分出來,生成xx.dll.js文件,通過manifest.json引用
webpack.dll.config.js
const?path?=?require("path");
const?webpack?=?require("webpack");
module.exports?=?{
????mode:?"production",
????entry:?{
????????react:?["react",?"react-dom"],
????},
????output:?{
????????filename:?"[name].dll.js",
????????path:?path.resolve(__dirname,?"dll"),
????????library:?"[name]"
????},
????plugins:?[
????????new?webpack.DllPlugin({
????????????name:?"[name]",
????????????path:?path.resolve(__dirname,?"dll/[name].manifest.json")
????????})
????]
};
package.json
"scripts":?{
????"dll-build":?"NODE_ENV=production?webpack?--config?webpack.dll.config.js",
??},
#2、splitChunks?拆包
optimization:?{
????????splitChunks:?{
????????????cacheGroups:?{
????????????????vendor:?{
????????????????????name:?'vendor',
????????????????????test:?/[\\/]node_modules[\\/]/,
????????????????????minSize:?0,
????????????????????minChunks:?1,
????????????????????priority:?10,
????????????????????chunks:?'initial'
????????????????},
????????????????common:?{
????????????????????name:?'common',
????????????????????test:?/[\\/]src[\\/]/,
????????????????????chunks:?'all',
????????????????????minSize:?0,
????????????????????minChunks:?2
????????????????}
????????????}
????????}
????},
六、骨架屏
用css提前占好位置,當資源加載完成即可填充,減少頁面的回流與重繪,同時還能給用戶最直接的反饋。圖中使用插件:react-placeholder

關于實現(xiàn)骨架屏還有很多種方案,用Puppeteer服務端渲染的挺多的
使用css偽類:只要css就能實現(xiàn)的骨架屏方案
等等
七、窗口化
原理:只加載當前窗口能顯示的DOM元素,當視圖變化時,刪除隱藏的,添加要顯示的DOM就可以保證頁面上存在的dom元素數(shù)量永遠不多,頁面就不會卡頓
圖中使用的插件:react-window

安裝:npm i react-window
引入:import { FixedSizeList as List } from 'react-window';
使用:
const?Row?=?({?index,?style?})?=>?(
??Row?{index}
);
?
const?Example?=?()?=>?(
??????height={150}
????itemCount={1000}
????itemSize={35}
????width={300}
??>
????{Row}
??
);
八、緩存
1、http緩存
keep-alive?
判斷是否開啟:看response headers中有沒有Connection: keep-alive 。
開啟以后,看network的瀑布流中就沒有 Initial connection耗時了
nginx設置keep-alive(默認開啟)
#0?為關閉
#keepalive_timeout?0;
#65s無連接?關閉
keepalive_timeout?65;
#連接數(shù),達到100斷開
keepalive_requests?100;
Cache-Control / Expires / Max-Age 設置資源是否緩存,以及緩存時間
Etag / If-None-Match 資源唯一標識作對比,如果有變化,從服務器拉取資源。如果沒變化則取緩存資源,狀態(tài)碼304,也就是協(xié)商緩存
Last-Modified / If-Modified-Since 通過對比時間的差異來覺得要不要從服務器獲取資源
更多HTTP緩存參數(shù)可參考:使用 HTTP 緩存:Etag, Last-Modified 與 Cache-Control
2、Service Worker
借助webpack插件WorkboxWebpackPlugin和ManifestPlugin,加載serviceWorker.js,通過serviceWorker.register()注冊
new?WorkboxWebpackPlugin.GenerateSW({
????clientsClaim:?true,
????exclude:?[/\.map$/,?/asset-manifest\.json$/],
????importWorkboxFrom:?'cdn',
????navigateFallback:?paths.publicUrlOrPath?+?'index.html',
????navigateFallbackBlacklist:?[
????????new?RegExp('^/_'),
????????new?RegExp('/[^/?]+\\.[^/]+$'),
????],
}),
new?ManifestPlugin({
????fileName:?'asset-manifest.json',
????publicPath:?paths.publicUrlOrPath,
????generate:?(seed,?files,?entrypoints)?=>?{
????????const?manifestFiles?=?files.reduce((manifest,?file)?=>?{
????????????manifest[file.name]?=?file.path;
????????????return?manifest;
????????},?seed);
????????const?entrypointFiles?=?entrypoints.app.filter(
????????????fileName?=>?!fileName.endsWith('.map')
????????);
????????return?{
????????????files:?manifestFiles,
????????????entrypoints:?entrypointFiles,
????????};
????},
}),
九、預加載 && 懶加載
1、preload
就拿demo中的字體舉例,正常情況下的加載順序是這樣的:

加入preload:

2、prefetch
場景:首頁不需要這樣的字體文件,下個頁面需要:首頁會以最低優(yōu)先級Lowest來提前加載
加入prefetch:
需要的頁面,從prefetch cache中取
webpack也是支持這兩個屬性的:webpackPrefetch 和 webpackPreload
#3、懶加載?
#圖片?
機械圖片

漸進式圖片(類似高斯模糊) 需要UI小姐姐出稿的時候指定這種格式

響應式圖片
原生模式:
?
#路由懶加載?
通過函數(shù) + import實現(xiàn)
const Page404 = () => import(/* webpackChunkName: "error" */'@views/errorPage/404');
十、ssr && react-snap
服務端渲染SSR,vue使用nuxt.js,react使用next.js
react-snap可以借助Puppeteer實現(xiàn)先渲染單頁,然后保留DOM,然后發(fā)送到客戶端
十一、體驗優(yōu)化
#白屏loading loading.html?

需要自取哦,還有種方式,使用webpack插件HtmlWebpackPlugin將loading資源插入到頁面中
"en">
??
????"UTF-8"?/>
????"viewport"?content="width=device-width,?initial-scale=1.0"?/>
????Loading
????
??
??
????"loadding">
??????
??????"animation-delay:?0.13s">
??????"animation-delay:?0.26s">
??????"animation-delay:?0.39s">
??????"animation-delay:?0.52s">
????
??
??
原文鏈接:
https://alexwjj.github.io/views/fe/others/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html#%E4%BA%8C%E3%80%81web-api



