背景
最近工作遇到产品的一个优化需求,产品说页面首次加载时间过长大概4-5秒左右,询问有什么好的解决办法,能不能优化一下,一秒出解决方案,直接上图。 以上玩笑,言归正传,页面首次加载时间过长是一版项目都会遇到的问题,接下来我将优化过程和思路分享下。
排查问题
查看项目webpack的splitChunck的方式
确实是按需加载,但是打包之后的dist包还是很大,现在需要排查包的问题就需要用到webpackd的构建包分析插件webpack-bundle-analyzer。
webpack-bundle-analyzer
webpack-bundle-analyzer会根据构建统计生成可视化页面,它会帮助你分析包中包含的模块们的大小,帮助提升代码质量和网站性能。
webpack-bundle-analyzer原理
这个插件做的工作本质就是分析在compiler.plugin('done', function(stats))
时传入的参数stats。Stats是webpack的一个统计类,对Stats实例调用toJson()方法,获取格式化信息。toJson方法支持的参数源码。
如何输出stats.json
在启动 Webpack 时,支持两个参数,分别是:
- --profile:记录下构建过程中的耗时信息;
- --json:以 JSON 的格式输出构建结果,最后只输出一个 .json 文件,这个文件中包括所有构建相关的信息。
在启动 Webpack 时带上以上两个参数,启动命令如下 webpack --profile --json > stats.json,
你会发现项目中多出了一个 stats.json 文件。 这个 stats.json 文件是给可视化分析工具使用的。
如图所示,webpack生成stats.json文件,里面包含 chunks,也就是 webpack 分块之后的文件模块,从里面可以看到依赖关系,文件尺寸等等信息,这个插件然后从stats.json中获取chunks然后最终使用Canvas画图。具体代码位于analyzer.js中的getViewerData方法。。
使用方法
可视化显示构建模块
- 打包出的文件中都包含了什么;
- 每个文件的尺寸在总体中的占比,一眼看出哪些文件尺寸大;
- 模块之间的包含关系;
- 每个文件的 Gzip 后的大小。
优化思路
在正常的项目开发中,为了提升开发速度我们会经常引入各种各样的第三方模块。但是有时候这些模块里面又包含了许多我们用不到的模块,在打包的时候又一并打包进去了,这就造成了构建包体积的"臃肿",所以我们要干掉没用到的模块。
通过构建模块可视化图,可以看出hightlight.js,html2canvas.js,jspdf.min.js这三个第三方库占比较大,在代码中查找发现这属于引入为却未使用的npm包,果断干掉,重新打包,优化前构建打包体积3.59M,优化后构建打包体积2.42M,体积小了1M多。
UE
首页加载过长,最直观的感受是全局loading过长,所有的请求数据加载采用全局过渡loading,这个时候就需要考虑使用局部过渡loading代替全局loading。
全局过渡loading和局部过渡loading对比
全局过渡loading示例
局部过渡lodaing示例
对比 | 全局过渡loading | 局部过渡loading |
---|---|---|
优点 | 1.限制用户操作(在一个请求pending过程中无法触发其他操作);2.数据全面渲染后统一展示。 | 1.数据返回立马显示,交互体验好;2.如果接口不稳定的话,我们的页面也可以正常展示某些部分 |
缺点 | 1.如果当前页面请求数量过多,或者接口速度过慢,会让用户等待很长时间。2.如果某个接口报错,用户体验教差 | 无法限制用户操作行为 |
局部过渡loading的方式
局部过渡loading的方式根据用途分为小loading、占位图、骨架屏等
小loading
一般图表类数据加载会用局部过渡小loading的方式,比如ant desigh的表格远程加载数据;
占位图
一般图片数据加载会使用占位图的方式,主要原因是为了避免回流,让图片加载完成后渲染处理后不改变文档的整体布局,性能得到优化,比如百度图片模块。
骨架屏
一般文章或者其他展示类情况会使用骨架屏的方式,比如掘金使用骨架屏过渡加载数据;
network
排查请求接口时长
从Time
上面看接口时长在130ms-350ms左右,比较正常,但是通过观察Waterfall
属性发现接口与接口之间时间间距较长,为什么并发请求是异步,没有同时开始呢?这个时候我们就要了解主流浏览器最大并发链接数。
浏览器最大并发链接数
一些主流浏览器一般对同一个服务器的并发连接个数都是有限制的。基于端口数量和线程切换开销的考虑,不可能无限量的并发请求,因此衍生出来了并发限制和HTTP/1.1的Keep alive。
所以,IE6/7在HTTP/1.1下的并发才2,但HTTP/1.0却是4。 而随着技术的发展,负载均衡和各类NoSQL的大量应用,基本已经足以应对C10K的问题。 但却并不是每个网站都懂得利用domain hash也就是多域名来加速访问。
因此,新的浏览器加大了并发数的限制,但却仍控制在8以内。
一些主流浏览器对HTTP 1.1和HTTP 1.0的最大并发连接数目,可以参考如下表格:
浏览器 | HTTP / 1.1 | HTTP / 1.1 |
---|---|---|
Chrome 4+ | 6 | 6 |
Safari 3,4 | 4 | 4 |
火狐 | 6 | 6 |
Opera 10.51+ | 8 | 8 |
IE 10 11 | 6 | 6 |
IE 9 | 10 | 10 |
IE 8 | 6 | 6 |
IE 6 7 | 2 | 4 |
从请求并发看问题
在了解浏览器最大并发链接数后,我们发现页面11个请求在谷歌浏览器下面并不是分两次并发,而是分五次并发,但是具体什么原因分析不出,这个时候就是需要借助chrome调试-性能分析工具performance
进行分析。
performance
performance面板可以记录和分析页面在运行时的所有活动。
总览区域
这里是整体的界面渲染的时候,每个时间段执行的事件顺序,通过上图我们就能知道我们每个时间段都做了什么,当鼠标放上去的时候,我们还可以大图的形式去查看我们每个时间段界面的渲染情况,Performance 就会将几个关键指标按照时间顺序做成图表的形式展现出来。
- FPS,表示每一秒的帧数,用来衡量页面动画的性能指标。fps图中绿色柱状越高表示体验越好。若出现红色长条则表示在该时间端出现长帧,可能影响用户体验。(目前大多数设备的屏幕刷新率为 60 次/秒)
- CPU,表示cpu的使用情况,其中颜色含义和底下的Summary模块中相同。从该行中颜色块的跨越时长可以分析哪类事件消耗的时间较长,从而找到性能瓶颈
- NET,每一个颜色条表示加载一种文件。蓝色表示html文件、黄色表示js文件、紫色表示样式文件、绿色表示媒体文件、灰色表示其他资源
- screenshots,对应每一时刻页面的屏幕快照
- V8 内存使用量 (堆内存HEAP) 等
性能面板(网络请求和主线程各种事件)
性能面板主要包括以下几部分
- Network 这里我们可以直观的看到资源加载的顺序与时长
- Interactions 用来记录用户交互操作,比如点击鼠标、输入文字、动画等
- Timings 用来记录一些关键的时间节点在何时产生的数据信息,诸如 FP、FCP、LCP 等
- Main 是Performance工具中比较重要的部分,记录了渲染进程中主线程的执行记录,点击main可以看到某个任务执行的具体情况
- Compositor 合成线程的执行记录,用来记录html绘制阶段 (Paint)结束后的图层合成操作
- Raster 光栅化线程池,用来让 GPU 执行光栅化的任务
- GPU GPU进程主线程的执行过程记录,如 可以直观看到何时启动GPU加速…
统计汇总区域
统计在我们检测性能的时间范围内各性能指标:
Summary统计图表
- Loading :加载时间
- Scripting :js计算时间
- Rendering :渲染时间
- Painting :绘制时间
- Other :其他时间
- Idle :浏览器闲置时间
Bottom-Up 事件时长排序
Call Tree 事件调用排序
Event Log 事件发生顺序排序
performance实践
下面通过这次项目优化过程performance实践例子,加深performance的相关参数的理解使用。
重点关注性能面板Network
从上图可以看出,两点明显信息:
- 前面存在几个低优先级请求,阻塞了优先级比较高请求资源接口;
- 页面中图片加载较多,每次可以同时加载的图片数有限;
这两种情况都会阻塞业务中高优先级资源获取(控制页面渲染的资源)。
window.requestIdleCallback
低优先级请求浏览器空闲时发生,不影响主线程进程 这里我们使用了window.requestIdleCallback 方法,将低优先级的请求放到callback函数内,将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环后执行后台低优先级工作。
window.requestIdleCallback有两个参数,一个是callback,在事件循环空闲时即将被调用的函数的引用,一个是options,只有一个timeout参数,如果指定了timeout并具有一个正值,并且尚未通过超时毫秒数调用回调,那么回调会在下一次空闲时期被强制执行。改完之后,通过Performance监测,节省了300ms左右加载时间。
雪碧图/精灵图
页面背景图片统一进行雪碧图处理,将多张图片合并到一张图片后,只需一次网络请求就可以将所需的资源全部下载,减小建立连接的消耗和资源浪费。
火焰图&&Summary统计图表
通过总览区域火焰图和统计报表javaScript脚本占CPU性能消耗比较大,这说明js执行的逻辑太多了,后续可以考虑优化js,提升部分性能。
结果对比
优化前加载一篇内容较长文章文章首次加载需要5-6s; 优化后首次加载只需要1.5s左右;
总结
通过使用通过performance不仅可以清楚了解项目性能和优化方向,还可以更直观的理解浏览器的渲染原理与工作流程,同时也能够将浏览器的系统架构、消息循环机制、渲染流水线等前端概念联系到一起。
同时也通过这次优化,也意识到ESlint在团队开发中的重要性,限制开发者引入未使用的npm包,也能在根源上杜绝无意义的npm包打入项目。还有背景图片走雪碧图处理,低优先级任务放window.requestIdleCallback中处理等等,一些好的开发规范也很重要,都能提升项目的成品质量。