前端页面性能优化之实战篇

3,821 阅读10分钟

背景

最近工作遇到产品的一个优化需求,产品说页面首次加载时间过长大概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.1HTTP / 1.1
Chrome 4+66
Safari 3,444
火狐66
Opera 10.51+88
IE 10 1166
IE 91010
IE 866
IE 6 724

从请求并发看问题

在了解浏览器最大并发链接数后,我们发现页面11个请求在谷歌浏览器下面并不是分两次并发,而是分五次并发,但是具体什么原因分析不出,这个时候就是需要借助chrome调试-性能分析工具performance进行分析。

performance

performance面板可以记录和分析页面在运行时的所有活动。

总览区域

这里是整体的界面渲染的时候,每个时间段执行的事件顺序,通过上图我们就能知道我们每个时间段都做了什么,当鼠标放上去的时候,我们还可以大图的形式去查看我们每个时间段界面的渲染情况,Performance 就会将几个关键指标按照时间顺序做成图表的形式展现出来。

  • FPS,表示每一秒的帧数,用来衡量页面动画的性能指标。fps图中绿色柱状越高表示体验越好。若出现红色长条则表示在该时间端出现长帧,可能影响用户体验。(目前大多数设备的屏幕刷新率为 60 次/秒)
  • CPU,表示cpu的使用情况,其中颜色含义和底下的Summary模块中相同。从该行中颜色块的跨越时长可以分析哪类事件消耗的时间较长,从而找到性能瓶颈
  • NET,每一个颜色条表示加载一种文件。蓝色表示html文件、黄色表示js文件、紫色表示样式文件、绿色表示媒体文件、灰色表示其他资源
  • screenshots,对应每一时刻页面的屏幕快照
  • V8 内存使用量 (堆内存HEAP) 等

性能面板(网络请求和主线程各种事件)

性能面板主要包括以下几部分

  1. Network 这里我们可以直观的看到资源加载的顺序与时长
  2. Interactions 用来记录用户交互操作,比如点击鼠标、输入文字、动画等
  3. Timings 用来记录一些关键的时间节点在何时产生的数据信息,诸如 FP、FCP、LCP 等
  4. Main 是Performance工具中比较重要的部分,记录了渲染进程中主线程的执行记录,点击main可以看到某个任务执行的具体情况
  5. Compositor 合成线程的执行记录,用来记录html绘制阶段 (Paint)结束后的图层合成操作
  6. Raster 光栅化线程池,用来让 GPU 执行光栅化的任务
  7. GPU GPU进程主线程的执行过程记录,如 可以直观看到何时启动GPU加速…

统计汇总区域

统计在我们检测性能的时间范围内各性能指标:

Summary统计图表

  • Loading :加载时间
  • Scripting :js计算时间
  • Rendering :渲染时间
  • Painting :绘制时间
  • Other :其他时间
  • Idle :浏览器闲置时间

Bottom-Up 事件时长排序

Call Tree 事件调用排序

Event Log 事件发生顺序排序

performance实践

下面通过这次项目优化过程performance实践例子,加深performance的相关参数的理解使用。

重点关注性能面板Network

从上图可以看出,两点明显信息:

  1. 前面存在几个低优先级请求,阻塞了优先级比较高请求资源接口;
  2. 页面中图片加载较多,每次可以同时加载的图片数有限;

这两种情况都会阻塞业务中高优先级资源获取(控制页面渲染的资源)。

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中处理等等,一些好的开发规范也很重要,都能提升项目的成品质量。

传送门

跟大家聊一下前端性能怎么优化

提升用户体验之局部过渡

webpack自动化架构入门