性能优化探索
性能优化指标
- 加载指标
- speedIndex速度指数:代表页面内容渲染所消耗的时间
- TTFB:是 Time to First Byte 的缩写,指的是浏览器开始收到服务器响应数据的时间(后台处理时间+重定向时间),是反映服务端响应速度的重要指标,TTFB 时间如果超过了 500 ms,用户在打开网页的时候就会感觉到明显的等待
- 页面加载时间
- 首次渲染: 渐进式加载
- 响应指标
- 交互动作的反馈时间
- 帧率FPS :是指画面每秒传输[帧数],通俗来讲就是指动画或视频的画面数,每秒钟帧数越多,所显示的动作就会越流畅
- 异步请求的完成时间
RAIL测量模型(量化性能优化指标/RAIL评估标准)
-
Response 响应:处理事件应在50ms以内完成,这样给用户的感受是瞬间就完成了
- 事件处理函数在50ms内完成,考虑到idle task的情况,事件会排队,等待时间大概在50ms。适用于click,toggle,starting animations等,不适用于drag和scroll。
- 复杂的js计算尽可能放在后台,如web worker,避免对用户输入造成阻塞
- 超过50ms的响应,一定要提供反馈,比如倒计时,进度百分比等。
-
Animation 动画:每10ms产生一帧
- 在一些高压点上,比如动画,不要去挑战cpu,尽可能地少做事,如:取offset,设置style等操作。尽可能地保证60帧的体验。
- 在渲染性能上,针对不同的动画做一些特定优化
-
Idle 空闲:尽可能增加空闲时间
- 用空闲时间来完成一些延后的工作,如先加载页面可见的部分,然后利用空闲时间加载剩余部分,此处可以使用 requestIdleCallback API
- 在空闲时间内执行的任务尽量控制在50ms以内,如果更长的话,会影响input handle的pending时间
- 如果用户在空闲时间任务进行时进行交互,必须以此为最高优先级,并暂停空闲时间的任务
-
Load 加载:在5s内完成内容的加载并可以交互
- 在手机设备上测试加载性能,选用中配的3G网络(400kb/s,400ms RTT),可以使用 WebPageTest 来测试
- 要注意的是,即使用户的网络是4G,但因为丢包或者网络波动,可能会比预期的更慢
- 禁用渲染阻塞的资源,延后加载
- 可以采用 lazy load,code-splitting 等 其他优化 手段,让第一次加载的资源更少
分析RAIL用的性能测量工具
-
chrome DevTools开发调试、性能评测
audit Lighthouse
throttling 调整网络吞吐量
performance
network -
Lighthouse网站整体质量评估,也可以直接使用chrome DevTools中的lighthouse
npm i -g lighthouse lighthouse http://www.bilibili.com
- WebpageTest多测试地点、全面性能报告解读
- waterfall chart请求瀑布图
- first view 首次访问
- repeat view 二次访问(反映缓存的优化情况)
- WebpageTest在线配置
- 本地配置WebpageTest(安装docker)
性能优化策略
一、渲染优化
-
关键渲染路径:
- javascript(触发视觉变化)
- style计算样式
- layout布局
- paint绘制
- composite复合
-
影响回流的操作 reflow
- 添加/删除元素
- 操作styles
- display: none
- offsetLeft, scrollTop, clientWidth
- 移动元素位置
- 修改浏览器大小,字体
-
避免布局抖动(layout thrashing )
- 避免回流
- transform/translate进行复合
- 读写分离
- 解决方案FastDom
- measure和mutate方法的使用
- 避免回流
-
复合线程(compositor thread)与图层(layer)
将页面拆分图层进行绘制再进行复合
利用DevTools了解网页的图层拆分情况
哪些样式仅影响复合 -
减少重绘的方案
利用devTools发现paint的瓶颈
will-change创建新的图层 -
高频事件处理函数 防抖 (requestAnimationFrame)
二、 代码优化
- code splitting代码拆分,按需加载
- tree shaking 代码减重
- 减少主线程工作量
避免长任务
避免超过1kb的行间脚本
使用rAF和rIC进行实践调度
- V8优化机制(js代码优化原理)
- 脚本流
- 字节码缓存
- 懒解析
-
函数优化
-
函数解析方式
- lazy parsing VS eager parsing
- 利用optimize.js优化初次加载时间
- 直接使用uglify.js优化
-
对象优化
- 以相同顺序初始化对象成员,避免隐藏类的调整
- 实例化后避免添加新属性
- 尽量使用Array代替array-like对象
// bad Array.prototype.forEach.call(arrObj, (value, index) => { console.log(`${index}:${value}`) })
// good const arr = Array.prototype.slice.call(arrObj, 0) arr.prototype.forEach.call(arrObj, (value, index) => { console.log(`${index}: finger ${value}`) })
- 避免读取超过数组的长度
functon foo (arr) { for(let i = 0; i <= arr.length; i++) { // 越界 if (arr[i] > 1000) { // 1. 造成undefined跟数组进行比较 2.造成沿原型链的额外查找 console.log(arr[i])} } } // 10, 100, 1000[]
- 避免元素类型转换
const arr = [3, 2, 1] // PACKED_SMI_ELEMENTS arr.push(4.4) // PACKED_DOUBLE_ELEMENTS 优化降级
三、HTML优化
-
减少iframes的使用(可以延迟加载)
<iframe id='a'></iframe> document.getElementById('a').setAttribute('src', 'url')
-
压缩空白符
-
避免节点深层级嵌套
-
避免使用table布局
-
删除注释
-
CSS&Javascript尽量外链
-
删除元素的默认属性
- 借助工具
html-minifier
四、 CSS优化
自右向左过滤样式
- 降低css对渲染的阻塞
- 利用GPU进行完成动画
- 使用contain: layout(对元素单独处理不会影响其他元素)
- 使用font-display属性
五、 资源的压缩与合并
- 减少http请求数量
- 减少请求资源本身的大小
HTML压缩
- 使用在线工具进行压缩
- 使用html-minifier等npm压缩工具
CSS压缩
- 使用在线工具进行压缩
- 使用clean-css等npm工具
JS压缩与混淆
- 使用在线工具进行压缩
- 使用webpack对js在构建时压缩
CSS JS文件合并
- 若干小文件
- 无冲突,服务相同的模块ok
- 优化加载NO
六、 图片优化方案
- 图片JPG/JPEG(压缩比高)
- 工具imagemin
- 使用场景:保证画质和色彩
- 缺陷:纹理和边缘比较粗糙
- png
- 体积比较大,适合图标和logo
- imagemin-pngquant
- webP
- svg
图片加载优化
1.懒加载
* 原生图片懒加载
* 第三方图片懒加载方案(verlok/lazyload、yall.js\Blazy)
2.渐进式图片(清晰度由低到高)
* progressive-image
* imageMagick
* libjpeg
* jpegtran
* jpeg-recompress
* imagemin
-
响应式图片
- srcset属性的使用(提供多个尺寸下对应的图片)
- sizes属性的使用()
- picture的使用
字体优化
- @font-face
- font-display: auto/block/swap/fallback/optional
- ajax+base64:解决了兼容性问题
七、webpack优化
- tree-shaking
- 上下文未用到的代码
- 基于es6 import export
- 全局作用于sideEffects: ['*.css']
- 注意babel默认配置的影响
- js压缩
- webpack4后引入uglifyjs-webpack-plugin
- 支持es6替换为terser-webpack-plugin
- 减小js文件体积
- 作用域提升
- babel7优化配置
- 在需要的地方引入polyfill, useBultIns: usage
- 辅助函数的按需引入
- 根据目标浏览器按需转换代码
webpack依赖优化
-
noParse
- 提高构建速度
- 直接通知webpack忽略较大的库
- 被忽略的库不能用import、require、define的引入方式
-
dllPlugin(主要用在开发环境打包)
- 避免打包时对不变的库重复构建
- 提高构建速度
基于webpack的代码拆分
把单个bundle文件拆分成多个小的bundle/chunk
提高首屏加载速度
- 手工定义人口
- 动态加载改造
- split-chunks提取共有代码,拆分业务代码与第三方库
optimizaton: {
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: 1 // 最少段数
}
基于webpack的资源压缩(minification)
- terser压缩js
- mini-css-extract-plugin 压缩css
- HtmlWebpackPlugin-minify压缩HTML
基于webpack的资源持久化缓存
- 每个打败的资源文件有唯一的hash值
- 修改后只有受影响的文件hash变化
- 充分利用浏览器缓存
output: {
path: `${__dirname}/build`,
filename: '[name].[hash].bundle.js'
chunkFilename: '[name].[chunkhash:8].bundle.js'
}
基于webpack应用的监测与分析
-
stats分析与可视化图,webpack chart
webpack --profile --json >stats.json
-
webpack-bundle-analyzer进行体积分析
source-map-explorer(基于sourcemap生成)
-
speed-measure-webpack-plugin速度分析
react按需加载
- React router基于webpack动态引入
- 使用reloadable高级组件
八、传输加载优化
- nginx配置启用压缩gzip, 对传输资源进行提及压缩,可高达90%
gzip on
gzip_min_length 1k // 最小文件
gzip_comp_level 6 // 压缩级别
gzip_types text/plain applicaton/javascript text/css text/javascript
- http启用Keep Alive
- 一个持久的TCP连接,节省了连接创建时间
- 基于nginx的配置默认开启
# keepalive_timeout 0 // 不开启
keepalive_timeout 65 // 开启
keepalive_requests 100 // 利用这个tcp链接可以发起的请求上限
- http缓存
- 提高重复访问时资源加载的速度
-
强缓存和协商缓存
协商缓存ETag和if-no-match
cache-control/expires
last-modify/if-modify-since
image.png
- service workers作用
- 加速重复访问
- 离线支持
注意 - 延长了首屏时间,但页面总加载时间减少
- 兼容性
- 只能在localhost或https下使用
- http/2的提升
-
二进制传输(提高传输效率,对头部进行压缩)
-
请求响应多路复用
nginx配置http2
image.png image.png image.png
配置自己生成签名的证书
image.png
- 重启nginx- https形式的localhost
- thisisunsafe
- nginx配置http2
-
server push 服务器推送
image.png image.png
-
- 只能工作在https下
- 适合较高的请求量
- 服务端渲染
- 首屏渲染优化
- 利于seo
- react react-dom next(服务端渲染插件)
九、前沿优化解决方案
- 拯救移动端图片-svg
- 从png到iconfont
- 多个图标一套字体,减少获取的请求数量和体积
- 矢量图形可伸缩
- 可以通过css修改大小和颜色
- 从iconfont到svg
- 保存了图片能力,支持多色彩
- 独立的矢量图形
- xml语法,搜索引擎seo和无障碍读屏软件读取
- 从png到iconfont
- 使用flexbox优化布局
- 更高性能的实现方案(相对于float:render和paint的时间更短)
- 容器有能力决定自元素的大小、顺序、对齐、间隔等
- 双向布局
- 优化资源加载顺序
- 浏览器默认安排资源加载优先级(html解析)
- 使用preload、prefetch调整优先级
- preload:提前加载较晚出现,但对当前页面比较重要的资源
- prefetch:提前加载后续路由需要的资源,优先级低
- webpack中也可以设置preload/prefetch
- 预渲染页面
- react-snap
- (配置postbuild)
- 使用ReactDom-hydrate
- 内敛样式,避免明显的FOUC(样式闪动)
- 大型单页应用的性能瓶颈:js下载+解析+执行
- ssr的主要问题:牺牲TTFB来补救Firdt Paint;实现复杂
- Pre-rendering打包时提前渲染页面,没有服务端参与
- react-snap
- windowing(窗口化)提高列表性能
- 加载大列表、大表单严重影响性能
- lazy loading仍然会让DOM变得过大
- windowing只渲染可建行,渲染和滚动的性能都会提升
react-window:可避免因为数据的更新而导致大量的重新渲染。- 配置一个一位列表List
- 配置一个二维列表Grid
- 配置滚动到指定元素
- 使用骨架组件减少布局移动(Layout Shift)
- skeleton/Placeholder 的作用
- 占位
- 提升用户感知性能
-
react-placeholder
image.png
- skeleton/Placeholder 的作用