blog
blog copied to clipboard
前端极致性能优化手册大全
前端极致性能优化手册大全
前端优化之路必不可少的知识点。
- 浏览器输入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];
}
- 根据客户端上报的请求头
accept: image/webp
,服务端进行自动判断,支持则返回webp
不支持则原图。
雪碧图
多个图片拼成一个图片,在利用 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加载 | 兼容性,国内部分浏览器有问题 |
结论:可以减少资源大小,但依赖外部服务,自建麻烦的话,放弃。