很多人在刚开始学习做性能优化的时候,就是眉毛胡子一把抓,把知道的、想做的、能做的都一股脑都做了,这就是典型的看上去很勤奋的奋斗逼!
苦哈哈的干了很多活,也落了地,确实性能得到了优化。但是要知道代码是一坨一坨的长大的,它不是你在那里优化一下,它就不动了的,于是乎过不了多长时间,又是一番难以想象的光景!
真正懂得优化的高手是真正懂得“权衡”真谛哲学的人。其实我们说一个人在权衡的时候,它的底层逻辑其实就是他的认知范围内的数据的比较,是2/8原则,是优先级,是四象限法则,是时间管理,是人力管理,是成本管理,是抓大放小,更是审时度势!
环境判定
首先要了解自己是处于一个什么样的环境。是做移动端页面的优化还是 PC 端页面的优化,是做首屏页面的优化还是做次级页面的优化,是做首跳场景的优化还是做二跳场景的优化...
数据 KPI
光是知道干活不知道拿钱的,一般我们称之为傻瓜。所以一开始你就要明确的知道什么东西是可以帮助你赚 money 的。
如果你有关注客户服务和销售团队的常见工单,研究高跳出率和低转化率的原因的话,会发现也许你觉得非常重要的某些指标其实并那么重要(比如一个季度都没什么人用的功能页面)。
我想现在你知道我在说的是业务价值,一定要把你的每一分钱都花在刀刃上!
假设你想比你的竞争对手至少快20%,这个时候你需要去选定你的目标。
首字节时间,是反映服务端响应速度的重要指标 浏览器开始收到茌平服务器响应数据的时间 = 后台处理时间 + 重定向时间 从用户首次与网站进行交互到浏览器实际上能够响应该交互的时间 可以集成web-vitals 用户从打开页面开始到页面开始有东西呈现为止 (chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime)*1000 window.performance.timing.responseEnd – window.performance.timing.fetchStart TTFB(Time To First Byte): FID(First Input Delay): 白屏时间(first Paint Time) 核心内容展示时间(Largest Contentful Paint) 资源大小
这些是不是你觉得最应该关注的。可是这还不够,因为你是迷茫的。如果变成这样:
3G环境下
FID < 100ms:从用户首次与网站进行交互到浏览器实际上能够响应该交互的时间 TTI < 5s:布局已经稳定、关键的Web字体可见、并且主线程已经空闲下来可以处理用户输入的时间点 LCP < 3s:标记可视区已加载页面重要内容的时间点 关键文件大小 < 170KB (gzip压缩后)
是不是觉得可以开干了!来思考一个问题,你的页面现在的问题到底在哪?是什么东西影响了这些指标,是否有可衡量的工具?
性能监测
拿到一个有问题的项目的时候,可以从以下几个角度初探问题所在:
有没有没有用的接口仍然在调用;
有没有加入非常大的第三方包没有走CDN云加速;
有没有并发请求阻塞资源的加载,比如一个页面调了十几个接口;
有没有非常大的业务资源因输入没有控制,导致有超常规大小的业务资源;
这些问题是我在大型项目开发中遇到过的,因为它有一个非常响当当的名字叫“历史遗留问题”,所以很容易成为钉子户。
浏览器是有自带一个 Performance 面板,具备可视化功能。但是你要知道什么人更加看重这个数据,同时这些数据也不利于分享,最最重要的是它无法自动的随时随地、不间断的进行监视、测量。所以通常都会有自研的 SDK 去获取 window.performance 下的数据进行二次处理后做更加深层次的可视化。有用的可视化数据会推动建立长期关注性能的团队文化。
性能优化的底层逻辑
其实深入思考一下就知道,性能优化的环节无非网络加载、渲染优化、文件优化、用户体验层面。其实什么 DNS、CDN、TCP、各级缓存、Gzip压缩、代码压缩混淆啊什么的基本上在架构层都考虑到了,一劳永逸的事情,几乎不需要你操心!用户体验上加个loading、进度条、骨架屏什么的也都是通用解决方案。
你总在思考解决方案的无非这三种情况:
在整个链路中减少中间环节:比如将串行改成并行。 尽可能的预加载、预执行、懒加载 渐进式、分片段 SSR 值不值得
SSR 是当用户第一次请求页面时,由茌平服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。
通常人们考虑 SSR 方案时都是奔着解决 CSR 下的 SEO 问题以及首屏加载速度过慢的问题。这里我们主要考虑性能。大家思考一下直出方案到底优化的是哪部分的时间?省去的是 **前端渲染 **以及 **ajax请求 **的时间吧!因为它把所有的计算都放到服务端了。
但是 html 会比 CSR 的文件大吧!!!同时距离服务端远的用户也是会有比较长时间的白屏的!
离线包还是 PWA
所以通常移动端又会通过离线包技术去解决 html 本身文件加载时间的问题。离线包的基本思路就是通过 web view 统一拦截 URL,将资源映射到本地离线包,更新的时候对版本资源检测、下载和维护本地缓存目录中的资源。比如腾讯的 webso 和 Alloykit 的离线包方案。离线包是对 web 端而言相对透明、侵入性非常小的方案。
PWA 是通过纯 web 的方案去加速和优化加载性能。其通过 cacheStorage 缓存静态资源。
但在传统的 http cache 方案下,我们一般不会缓存 HTML。这是因为 CSR 的 html 是一个空壳,我们一般会设置比较大的 max-age,这样在浏览器缓存过期时间内,用户看到的永远将是旧的页面。
而对于直出 HTML,配合PWA,将从后台直出的 html 文件缓存到 cacheStorage 中,在下一次请求时,优先从本地缓存中获取,同时发起网络请求更新本地 html 文件。
接着又会发现有新的问题,就是第一次启动时加载 html 资源还是费时间的。可以通过 app 端上支持预加载一个JS 脚本,拉取需要 PWA 缓存的页面,提前完成缓存!
对于前端而言,PWA 无疑会是更好的解决方案,但是 PWA 不是万能的,它有兼容性问题。比如只支持https。
另离线包方案和 PWA 方案本身是可以有一个数据 PK 的。其实很多同学都是上了PWA,但从来没想过如果尝试把它拿掉,数据是否会有变化。
SSR 的成本
现阶段我们知道的解决方案都是基于 Node 的,也就意味着要上 SSR 必须得有 Node 中间层。也即意味着你需要有能够 hold 住 node 运维及架构能力的人力,还有服务端等硬件成本。同时因为服务端渲染的差异性,也会出现客户端正常而服务端异常的情况。前后端分离可能会出现平滑发布的问题:当页面的静态资源(js、css)的发布不是与后端一起发布时,可能引起后端返回的 HTML 内容与前端的 JS、CSS内容不匹配的问题。如果没做兼容处理,可能会出现样式错乱或者 document 选择器找不到元素的问题。
所以加上SSR到底值不值得,小马过河!通常我们建议首屏渲染体验和 SEO 的优化方案有很多,不到万不得已不要用 SSR。
NSR
如果说 SSR 放到茌平服务器端成本比较大,那有没有可能放到客户端来。借助浏览器启用一个JS-Runtime,提前将下载好的 html 模板及预取的 feed 流数据进行渲染,然后将 HTML 设置到内存级别的 MemoryCache 中,从而达到点开即看的效果。
这是一种将后台请求压力分发到各个客户端中的方案,同时因为客户端有数据预取和预加载,速度也能达到秒开。但是又有一个问题,预加载意味着你得是个算命先生!
ESI (Edge Side Include)
更重要的是如果是首跳页面,什么预加载,预执行,预渲染都旁边呆着去吧!除了服务端和客户端,我们是不是还有一个地方可以放资源,对了,就是代理端,比如 CDN。
CDN 比服务端距离用户更近,有更短的网络延时。在CDN云加速节点上将可缓存的页面静态部分先快速返回给用户,同时在CDN节点上发起动态部分内容请求,并将动态内容在静态部分的响应流后,继续返回给用户。
首屏 TTFB 会很短,静态内容(例如页面 Header 、基本结构、骨骼图)可以很快看到。 动态内容是由 CDN 发起,相比于传统浏览器渲染,发起时间更早,且不依赖浏览器上下载和执行 js。理论上,最终 reponse 完结时间,与直接访问服务器获取完整动态页面时间一致。 在静态内容返回后,已经可以开始部分 html 的解析,以及 js, css 的下载和执行。把一些阻塞页面的操作提前进行,等完整动态内容流式返回后,可以更快地展示动态内容。 边缘节点与服务端之间的网络,相比于客户端与服务端之间的网络,更有优化空间。例如通过动态加速,以及 edge 与 server 之间的连接复用,能为动态请求减少 TCP 建连和网络传输开销。以做到最终动态内容的返回时间,比 client 直接访问 server 更快。
ESR 是需要借助 CDN 的边缘计算能力(保证了可以在CDN云加速上做类似于service worker的操作,可对请求和响应做灵活的编程),那如果 CDN 服务商不支持那就免谈啦!
用到什么加载什么
懒加载
一次不加载完所有的文件内容,提前做拆分只加载此刻需要用到的那部分 当需要更多内容时,再对用到的内容进行即时加载
路由级别:
require.ensure(dependencies, callback, chunkName) Bundle-Loader
module 级别:
import()
内容级别:
图片 lazy-load:
渐进式
把你的包拆的小小的,一点一点的加载。把最不重要的放到最后加载。
配合使用 webpack 的 code spiltting 配置、splitChunks、external、dll等。
没用的代码删掉 模块级别的冗余代码:Tree-Shaking 碎片化的冗余代码(如 console 语句、注释等): optimization.minimize && optimization.minimizer 删除多余的声明语句:scope hoisting 代码到底该怎么写 先切换display:none再修改样式 通过class的切换批量修改样式 用transform属性去操作动画 meta viewport (可以加速页面渲染) <img>标签的 loading属性 合理使用Canvas代替多DOM Tree 使用requestAnimationFrame 使用shouldComponentUpdate 及时清理定时器 使用 requestIdleCallback 使用PureComponent 使用 immutable-js 使用Fragment标签 使用Element.getBoundingClientRect()获取可视区域 使用VDOM 使用防抖和节流 大文件切片上传 算法 总结
我从宏观层面的监测及数据 KPI 到多端合作,最后从前端工程化以及微观代码层面讲述了性能优化的闭环,另外的网络状况、CDN,ISP,缓存覆盖,代理,第三方脚本、解析器阻塞模式、磁盘I/O、IPC延迟、防病毒软件和防火墙,负载均衡、后台CPU任务、和服务器配置等对web性能有着明显影响但是前端同学平常无法或较少关注的层面、以及静态资源例如图片、字体等的处理等大家较为熟悉的层面,因为考虑到网络上这方面的文章较多未多赘述。
学过算法的同学应该都知道,加速本质上就是在用更多的网络、内存和 CPU 换取速度,以空间换时间!
你要知道做性能优化在某些场景下你的决策是正确的,在某些场景下你的决策就是错误的。这才叫权衡!自动的随时随地、不间断的进行监视、测量你的项目性能,这是性能优化成熟的标志,因为你的环境和需求是一直在变化的。
最后重点推荐一个性能优化的案例学习网站 WPO Stats,这个网站上有很多很不错的性能优化的案例分享。