前端页面性能优化方式
source link: http://blog.poetries.top/2021/02/08/fe-youhua/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
这里可以看到资源加载详情,初步评估影响 页面性能
的因素。鼠标右键可以自定义选项卡,页面底部是当前加载资源的一个概览。 DOMContentLoaded
DOM渲染完成的时间, Load
:当前页面所有资源加载完成的时间
思考:如何判断哪些资源对当前页面加载无用,做对应优化?
shift + cmd + P 调出控制台的扩展工具,添加规则
监控页面性能变化
瀑布流waterfal
-
Queueing
浏览器将资源放入队列时间 -
Stalled
因放入队列时间而发生的停滞时间 -
DNS Lookup
DNS解析时间 -
Initial connection
建立HTTP连接的时间 -
SSL
浏览器与服务器建立安全性连接的时间 -
TTFB
等待服务端返回数据的时间 -
Content Download
浏览器下载资源的时间
2、Lighthouse
First Contentful Paint Speed Index Time to Interactive
根据chrome的一些策略自动对网站做一个质量评估,并且会给出一些优化的建议
3、Peformance
对网站最专业的分析
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-map
webpack.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、监听视窗激活状态
// 窗口激活状态监听 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、监听网络变化
网络变化时给用户反馈网络问题,有时候看直播的时候自己的网络卡顿,直播平台也会提醒你或者自动给你切换清晰度
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、更多计算规则
-
DNS
解析耗时:domainLookupEnd - domainLookupStart
-
TCP
连接耗时:connectEnd - connectStart
-
SSL
安全连接耗时:connectEnd - secureConnectionStart
- 网络请求耗时 (
TTFB
):responseStart - requestStart
- 数据传输耗时:
responseEnd - responseStart
-
DOM
解析耗时:domInteractive - responseEnd
- 资源加载耗时:
loadEventStart - domContentLoadedEventEnd
-
First Byte
时间:responseStart - domainLookupStart
- 白屏时间:
responseEnd - fetchStart
- 首次可交互时间:
domInteractive - fetchStart
-
DOM Ready
时间:domContentLoadEventEnd - fetchStart
- 页面完全加载时间:
loadEventStart - fetchStart
-
http
头部大小:transferSize - encodedBodySize
- 重定向次数:
performance.navigation.redirectCount
- 重定向耗时:
redirectEnd - redirectStart
三、雅虎军规
关于雅虎军规,你知道的有多少条,平时写用到的又有哪些?针对以下规则,我们可以做很多优化工作
1、减少cookie传输
cookie
传输会造成带宽浪费,可以:
- 减少
cookie
中存储的东西 - 静态资源不需要
cookie
,可以采用其他的域名,不会主动带上cookie
2、避免过多的回流与重绘
连续触发页面回流操作
let cards = document.getElementsByClassName("MuiPaper-rounded"); const update = (timestamp) => { for (let i = 0; i <cards.length; 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
进行优化,将对dom的 读和写
分离,合并
let cards = document.getElementsByClassName("MuiPaper-rounded"); const update = (timestamp) => { for (let i = 0; i < cards.length; 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
架构做的很牛逼,有兴趣的可以去了解一下调度算法在fiber中的实践
四、压缩
1、Gzip
开启方式可参考: nginx开启gzip
还有一种方式:打包的时候生成gz文件,上传到服务器端,这样就不需要nginx来压缩了,可以降低服务器压力。 可参考: gzip压缩文件&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
具体优缺点可参考: webpack常用的三种JS压缩插件 。 压缩原理
简单的讲就是去除一些空格、换行、注释,借助es6模块化的功能,做了一些 tree-shaking
的优化。同时做了一些代码混淆,一方面是为了更小的体积,另一方面也是为了源码的安全性。
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的特点
http2_push: 'xxx.jpg'
具体升级方式也很简单,修改一下 nginx
配置,方法请自行 Google
五、webpack优化
上文中也提到了部分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", },
webpack4不需要配置dll了,因为webpack4打包性能已经足够优化,vue-cli3都已经移除 dll
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
关于实现骨架屏还有很多种方案,用 Puppeteer
服务端渲染的挺多的
使用css伪类: 只要css就能实现的骨架屏方案
七、窗口化
原理:只加载当前窗口能显示的DOM元素,当视图变化时,删除隐藏的,添加要显示的DOM就可以保证页面上存在的dom元素数量永远不多,页面就不会卡顿
图中使用的插件: react-window
安装: npm i react-window
引入: import { FixedSizeList as List } from 'react-window';
使用:
const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
八、缓存
1、http缓存
keep-alive
判断是否开启:看 response headers
中有没有 Connection: keep-alive
。开启以后,看 network
的瀑布流中就没有 Initial connection
耗时了
nginx
设置 keep-alive
(默认开启)
# 0 为关闭 #keepalive_timeout 0; # 65s无连接 关闭 keepalive_timeout 65; # 连接数,达到100断开 keepalive_requests 100;
Cache-Control / Expires / Max-Age
设置资源是否缓存,以及缓存时间
Etag / If-None-Match
资源唯一标识作对比,如果有变化,从服务器拉取资源。如果没变化则取缓存资源,状态码304,也就是协商缓存
Last-Modified / If-Modified-Since
通过对比时间的差异来觉得要不要从服务器获取资源
更多HTTP缓存参数可参考: 使用 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:
<link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.119.woff2" as="font" crossorigin="anonymous"/> <link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.118.woff2" as="font" crossorigin="anonymous"/> <link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.116.woff2" as="font" crossorigin="anonymous"/>
2、prefetch
场景:首页不需要这样的字体文件,下个页面需要:首页会以最低优先级 Lowest
来提前加载
加入 prefetch
:
需要的页面,从 prefetch cache
中取
webpack也是支持这两个属性的: webpackPrefetch 和 webpackPreload
3、懒加载
图片
机械图片
渐进式图片(类似高斯模糊)
需要UI小姐姐出稿的时候指定这种格式
响应式图片
原生模式: <img src="./img/index.jpg" sizes="100vw" srcset="./img/dog.jpg 800w, ./img/index.jpg 1200w"/>
路由懒加载
通过 函数 + import
实现
const Page404 = () => import(/* webpackChunkName: "error" */'@views/errorPage/404');
十、ssr && react-snap
- 服务端渲染
SSR
,vue使用nuxt.js
,react
使用next.js
-
react-snap
可以借助Puppeteer
实现先渲染单页,然后保留DOM
,发送到客户端
十一、体验优化
白屏loading
loading.html需要自取哦,还有种方式,使用 webpack
插件 HtmlWebpackPlugin
将loading资源插入到页面中
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Loading</title> <style> body { margin: 0; } #loadding { position: fixed; top: 0; bottom: 0; display: flex; width: 100%; align-items: center; justify-content: center; } #loadding > span { display: inline-block; width: 8px; height: 100%; margin-right: 5px; border-radius: 4px; -webkit-animation: load 1.04s ease infinite; animation: load 1.04s ease infinite; } @keyframes load { 0%, 100% { height: 40px; background: #98beff; } 50% { height: 60px; margin-top: -20px; background: #3e7fee; } } </style> </head> <body> <div id="loadding"> <span></span> <span style="animation-delay: 0.13s"></span> <span style="animation-delay: 0.26s"></span> <span style="animation-delay: 0.39s"></span> <span style="animation-delay: 0.52s"></span> </div> </body> <script> window.addEventListener("DOMContentLoaded", () => { const $loadding = document.getElementById("loadding"); if (!$loadding) { return; } $loadding.style.display = "none"; $loadding.parentNode.removeChild($loadding); }); </script> </html>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK