前端性能精进之优化方法论(一)——测量 (上)

简介: 前端性能精进之优化方法论(一)——测量

性能优化的重要性不言而喻,Google 的研究表明,当网站达到核心 Web 指标(Core Web Vitals)阈值时,用户放弃加载网页的可能性会降低 24%。

  如何科学地定位到网页的性能瓶颈,就需要找到一个合理的方式来测量和监控页面的性能,确定优化的方向。

  前端的性能监控分为 2 种:

  • 第一种是合成监控(Synthetic Monitoring,SYN),模拟网页加载或脚本运行等方式来测量网页性能,输出性能报告以供参考,常用的工具有 Chrome DevTools 的 Performance 面板、LighthouseWebPageTest 等。
  • 第二种是真实用户监控(Real User Monitoring,RUM),采集真实用户所访问到的页面数据,通过 PerformancePerformanceObserver 等 API 计算得到想要的性能参数,各种第三方的性能监控的 SDK 就属于此类。

  本文的示例代码摘取自 shin-monitor,一款开源的前端监控脚本。

  为了便于记忆,特将此系列的所有重点内容浓缩成一张思维导图。

  


一、Performance


  W3C 在 2012 年制订了第一版测量网页性能的规范:Navigation Timing。下图提供了页面各阶段可用的性能计时参数。

  

  注意,若重定向是非同源,那么带下划线的 redirectStart、redirectEnd、unloadStart、unloadEnd 四个值将一直都是 0。

  W3C 在几年后又制订了第二版的规范:Navigation Timing Level 2,如下图所示。

  

  注意,在浏览器中,读取 unloadEventStart 的值后,会发现这个时间点并不会像图中那样在 fetchStart 之前,因为 unload 不会阻塞页面加载。

  接下来,会用代码来演示性能参数的计算,后文中的 navigationStart 参数其实就是 startTime。

1)性能对象

  第一版获取性能参数的方法是调用 performance.timing,第二版的方法是调用 performance.getEntriesByType('navigation')[0]。

  前者得到一个 PerformanceTiming 对象,后者得到一个 PerformanceNavigationTiming 对象。

  在下面的代码中,若当前浏览器不支持第二版,则回退到第一版。不过,目前主流的浏览器对第一版的支持也比较好。

const timing = performance.getEntriesByType('navigation')[0] || performance.timing;

  以我公司为例,投放到线上的页面,其中只有大概 5.5% 的用户读取的第一版。

  2023-02-27 注意,PerformanceNavigationTiming 继承了 PerformanceResourceTiming

  在 iOS 设备中,若 SDK 涉及跨域,并且其响应没有声明 timing-allow-origin 首部,那么 PerformanceResourceTiming 中的大部分属性都是 0。

  包括 responseStart、connectStart、domainLookupStart 等都为 0,若 responseStart 为 0,那就会影响 TTFB 的计算,其值也会一直为 0。

  可以将 timing-allow-origin 设为星号,或指定域名,如下所示。

Timing-Allow-Origin: *
Timing-Allow-Origin: https://www.pwstrick.com

  2023-03-14 虽然添加了 timing-allow-origin,但是统计结果中 TTFB 仍然包含大量的 0。

  经过抓包发现,是因为没有请求服务器中的 SDK,而是直接读取了客户端中的缓存。

  为了让客户端每次都去校验资源是否需要更新(即破缓存),就在 SDK 的响应头中增加 Cache-Control: no-cache。

2)fetchStart

  从上面的计时图中可知,在 fetchStart 之前,浏览器会先处理重定向。

  重定向的本质是在服务器第一次响应时,返回 301 或 302 状态码,让客户端进行下一步请求。

  会多走一遍响应流程,若不是像鉴权、短链等有意义的重定向,都应该要避免。

  比较常见的有浏览器强制从 HTTP 页面重定向到对应的 HTTPS 页面,以及主域名的重定向,例如从 https://pwstrick.com 重定向至 https://www.pwstrick.com

  由于浏览器安全策略的原因,不同域名之间的重定向时间,是无法精确计算的,只能统计 fetchStart 之前的总耗时。

  fetchStart 还会包含新标签页初始化的时间,但并不包括上一个页面的 unload 时间。

  由此可知,startTime 其实是在卸载上个页面之后开始统计的。 fetchStart 最主要的优化手段就是减少重定向次数。

  例如若页面需要登录,则做成一个弹框,不要做页面跳转,还例如在编写页面时,不要显式地为 URL 添加协议。

3)TCP

  TCP 在建立连接之前,要经过三次握手,若是 HTTPS 协议,还要包括 SSL 握手,计算规则如下所示。

/**
 * SSL连接耗时
*/
const sslTime = timing.secureConnectionStart;
connectSslTime = sslTime > 0 ? timing.connectEnd - sslTime : 0;
/**
 * TCP连接耗时
*/
connectTime = timing.connectEnd - timing.connectStart;

  在建立连接后,TCP 就可复用,所以有时候计算得到的值是 0。

  若要减少 TCP 的耗时,可通过减少物理距离、使用 HTTP/3 协议等方法实现。

  还有一种方法是通过 preconnect 提前建立连接,如下所示,浏览器会抢先启动与该来源的连接。

<link rel="preconnect" href="https://pwstrick.com"/>

4)TTFB

  TTFB(Time To First Byte)是指读取页面第一个字节的时间,即从发起请求到服务器响应后收到的第一个字节的时间差,用于衡量服务器处理能力和网络的延迟。

  TTFB 包括重定向、DNS 解析、TCP 连接、网络传输、服务器响应等时间消耗的总和,计算规则就是 responseStart 减去 redirectStart。

TTFB = timing.responseStart - timing.redirectStart;

  其实,TTFB 计算的是整个通信的往返时间(Round-Trip Time,RTT),以及服务器的处理时间。

  所以,设备之间的距离、网络传输路径、数据库慢查询等因素都会影响 TTFB。

  一般来说,TTFB 保持在 75ms 以内会比较完美,而在 200ms 以内会比较理想,若超过 500ms,用户就会感觉到明显地白屏。

  TTFB 常用的优化手段包括增加 CDN 动态加速、减少请求的数据量、服务器硬件升级、优化后端代码(引入缓存、慢查询优化等服务端的工作)。

5)FP 和 FCP

  白屏(First Paint,FP)也叫首次绘制,是指屏幕从空白到显示第一个画面的时间,即渲染树转换成屏幕像素的时刻。

  这是用户可感知到的一个性能参数,1 秒内是比较理想的白屏时间。

  白屏时间的计算规则有 2 种:

  • 第一种是读取 PerformancePaintTiming 对象,再减去 fetchStart。
  • 第二种是通过 responseEnd 和 fetchStart 相减。
const paint = performance.getEntriesByType("paint");
if (paint && timing.entryType && paint[0]) {
  firstPaint = paint[0].startTime - timing.fetchStart;
} else {
  firstPaint = timing.responseEnd - timing.fetchStart;
}

  在实践中发现,每天有大概 2 千条记录中的白屏时间为 0,而且清一色的都是苹果手机。

  一番搜索后,了解到,当 iOS 设备通过浏览器的前进或后退按钮进入页面时,fetchStart、responseEnd 等性能参数很可能为 0。

  还发现当初始页面的结构中,若包含渐变的效果时,1 秒内的白屏占比会从最高 94% 降低到 85%。

  注意,PerformancePaintTiming 包含两个性能数据,FP 和 FCP,理想情况下,两者的值可相同。

  FCP(First Contentful Paint)是指首次有实际内容渲染的时间,测量页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。

  内容是指文本、图像(包括背景图像)、svg 元素或非白色的 canvas 元素,不包括 iframe 中的内容。

  网站性能测试工具 GTmetrix 认为 FCP 比较理想的时间是控制在 943ms 以内,字节的标准是控制在 1.8s 内。

if (paint && timing.entryType && paint[1]) {
  firstContentfulPaint = paint[1].startTime - timing.fetchStart;
} else {
  firstContentfulPaint = 0;
}

  影响上述两个指标的主要因素包括网络传输和页面渲染,优化的核心就是降低网络延迟以及加速渲染。

  优化手段包括剔除阻塞渲染的 JavaScript 和 CSS、优化图像、压缩合并文件、延迟加载非关键资源、使用 HTTP/2 协议、SSR 等。

6)DOM

  在性能计时图中,有 4 个与 DOM 相关的参数,包括 domInteractivedomCompletedomContentLoadedEventStart 和 domContentLoadedEventEnd。

  domInteractive 记录的是在加载 DOM 并执行网页的阻塞脚本的时间。

  在这个阶段,具有 defer 属性的脚本还没有执行,某些样式表加载可能仍在处理并阻止页面呈现。

  domComplete 记录的是完成解析 DOM 树结构的时间。

  在这个阶段,DOM 中的所有脚本,包括具有 async 属性的脚本,都已执行。并且开始加载 DOM 中定义的所有页面静态资源,包括图像、iframe 等。

  loadEventStart 会紧跟在 domComplete 之后。在大多数情况下,这 2 个指标是相等的。在 loadEventStart 之前可能的延迟将由 onReadyStateChange 引起。

  由 domInteractive 和 domComplete 两个参数可计算出两个 DOM 阶段的耗时,如下所示。

initDomTreeTime = timing.domInteractive - timing.responseEnd;    // 请求完毕至 DOM 加载的耗时
parseDomTime = timing.domComplete - timing.domInteractive;       // 解析 DOM 树结构的耗时

  若 initDomTreeTime 过长的话,就需要给脚本瘦身了。若 parseDomTime过长的话,就需要减少资源的请求了。

  DOMContentLoaded(DCL)紧跟在 domInteractive 之后,该事件包含开始和结束两个参数,jQuery.ready() 就是封装了此事件。

  该事件会在 HTML 加载完毕,并且 HTML 所引用的内联和外链的非 async/defer 的同步 JavaScript 脚本和 CSS 样式都执行完毕后触发,无需等待图像和 iframe 完成加载。

  由 domContentLoadedEventEnd 可计算出用户可操作时间,即 DOM Ready 时间。

domReadyTime = timing.domContentLoadedEventEnd - navigationStart;        // 用户可操作时间(DOM Ready时间)

  注意,若 domContentLoadedEventEnd 高于 domContentLoadedEventStart,则说明该页面中也注册了此事件。

  与 DCL 相比,load 事件触发的时机要晚的多。

  它会在页面的 HTML、CSS、JavaScript(包括 async/defer)、图像等静态资源都已经加载完之后才触发。

相关文章
|
7天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
10天前
|
存储 缓存 NoSQL
Redis多级缓存指南:从前端到后端全方位优化!
本文探讨了现代互联网应用中,多级缓存的重要性,特别是Redis在缓存中间件的角色。多级缓存能提升数据访问速度、系统稳定性和可扩展性,减少数据库压力,并允许灵活的缓存策略。浏览器本地内存缓存和磁盘缓存分别优化了短期数据和静态资源的存储,而服务端本地内存缓存和网络内存缓存(如Redis)则提供了高速访问和分布式系统的解决方案。服务器本地磁盘缓存因I/O性能瓶颈和复杂管理而不推荐用于缓存,强调了内存和网络缓存的优越性。
30 1
|
1天前
|
缓存 前端开发 JavaScript
优化前端性能的五大技巧
在当今快节奏的网络世界中,优化前端性能是网站开发中至关重要的一环。本文将介绍五种有效的技巧,帮助开发者提升前端性能,提升用户体验和网站效率。
|
7天前
|
存储 缓存 前端开发
【Flutter前端技术开发专栏】Flutter中的图片加载与缓存优化
【4月更文挑战第30天】本文探讨了 Flutter 中如何优化图片加载与缓存,以提升移动应用性能。通过使用图片占位符、压缩裁剪、缓存策略(如`cached_network_image`插件)以及异步加载和预加载图片,可以显著加快加载速度。此外,利用`FadeInImage`、`FutureBuilder`和图片库等工具,能进一步改善用户体验。优化图片处理是提升Flutter应用效率的关键,本文为开发者提供了实用指导。
【Flutter前端技术开发专栏】Flutter中的图片加载与缓存优化
|
7天前
|
缓存 前端开发 数据安全/隐私保护
【Flutter 前端技术开发专栏】Flutter 中的键盘处理与输入框优化
【4月更文挑战第30天】本文探讨了Flutter中键盘处理与输入框优化的关键技术,包括监听键盘显示隐藏、焦点管理、键盘类型适配、输入框高度自适应、处理键盘遮挡问题及性能优化。通过使用WidgetsBindingObserver、FocusNode和TextInputType等工具,开发者能提升用户体验,确保输入框在各种场景下的良好表现。实例分析和实践建议有助于开发者将这些方法应用于实际项目。
【Flutter 前端技术开发专栏】Flutter 中的键盘处理与输入框优化
|
7天前
|
前端开发 UED 开发者
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
【4月更文挑战第30天】Flutter开发中,优化列表和滚动视图至关重要。本文介绍了几种优化方法:1) 使用`ListView.builder`和`GridView.builder`实现懒加载;2) 复用子组件以减少实例创建;3) 利用`CustomScrollView`和`Slivers`提升滚动性能;4) 通过`NotificationListener`监听滚动事件;5) 使用`KeepAlive`保持列表项状态。掌握这些技巧能提升应用性能和用户体验。
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
|
7天前
|
缓存 前端开发 JavaScript
如何优化前端性能:关键技巧与最佳实践
在当今数字化时代,前端性能优化是构建高效用户体验的关键。本文探讨了一系列关键技巧和最佳实践,帮助开发人员提升前端应用的性能表现。从资源加载到代码优化,从图片处理到网络请求,我们将深入探讨如何通过各种手段使前端应用更加高效。
|
8天前
|
前端开发 JavaScript 网络协议
【专栏】前端性能优化之 Performance 神器
【4月更文挑战第29天】本文探讨了前端性能优化中的 Performance 工具,它能帮助开发者分析页面加载速度和交互体验。通过 Performance,可检测资源加载时间、JavaScript 执行时间、重绘与回流等关键指标,找到性能瓶颈。文中列举了三个实践案例,如优化图片加载、减少 JavaScript 执行时间和避免重绘回流,展示如何利用 Performance 改进页面性能,提升用户体验。开发者应定期使用 Performance 分析并学习新优化技术,以适应Web开发的快速发展。
|
9天前
|
缓存 前端开发 算法
前端需要加载一个大体积的文件时,可以这么优化
前端需要加载一个大体积的文件时,可以这么优化
|
9天前
|
缓存 前端开发 JavaScript
如何优化前端网页加载速度:实用技巧大揭秘
在当今互联网时代,快速加载的网页是用户体验的关键。本文将介绍一些实用的前端优化技巧,从减少HTTP请求到使用CDN加速,帮助开发人员提高网页加载速度,提升用户满意度。