浏览器衡量性能 - Navigation Timing API详解

393 阅读7分钟

Navigation Timing API 是一个浏览器提供的接口,用于获取有关网页加载和性能的详细时间信息。

它通过 window.performance 对象提供了一系列的属性和方法,帮助开发者精确测量网页加载过程中各个关键阶段的时间

Navigation Timing 的处理模型

Level 1 的规范在W3C的议程上,它已经功成身退,让位给了精度更高,功能更强大,层次更分明的 Level 2,此文也仅讨论Level 2,对Level 1 感兴趣的可以自行查阅

Snipaste_2024-07-23_10-02-04.jpg

指标解读

属性介绍备注
navigationStart表示从上一个文档卸载结束时的时间 / 浏览器开始导航到当前页面的时间如果没有上一个文档,这个值和fetchStart相等
unloadEventStart表示前一个网页开始执行unload事件的时间如果无前一个网页,或者网页不同源,则值为0
unloadEventEnd表示前一个网页unload事件执行完毕的时间如果无前一个网页,或者网页不同源,则值为0
redirectStart第一个HTTP重定向开始的时间要有跳转,且是同域名内的跳转,否则值为0
redirectEnd最后一个HTTP重定向完成的时间要有跳转,且是同域名内的跳转,否则值为0
fetchStart浏览器开始获取页面的时间 / 浏览器准备好使用HTTP请求抓取网络获取资源(通常是当前页面的 HTML 文档)的时间发生在真正开始建立网络连接之前,例如在进行 DNS 查找之前,检测本地缓存之前
domainLookupStart / domainLookupEndDNS域名查询开始 / 结束的时间如果使用了本地缓存(即无DNS查询) 或使用了持久连接,则与fetchStart的值相等
connectStart开始/重新与服务器建立HTTP(TCP)连接的时间如果是持久连接,则与fetchStart值相等;
connectEndHTTP(TCP)完成建立的时间(完成握手)①它表示连接建立完成(同时也是 TCP/TLS 握手结束)的时间; ②如果是持久连接,则与fetchStart值相等;
secureConnectionStart开始/重新与服务器建立HTTPS(TLS)连接的时间①与之对应的也是 connectEnd; ②如果不是安全连接,则值为0;③使用的话需要兼容:secureConnectionStart / connectStart - connectEnd
requestStart浏览器开始发送请求的时间 / HTTP请求开始读取真实文档的时间(建立连接完成)包括从本地读取缓存
responseStartHTTP开始响应 / 浏览器收到响应的第一个字节的时间包括从本地读取缓存
responseEnd浏览器响应结束 / HTTP响应全部接收完成的时间(获取到最后一个字节)包括从本地读取缓存
domLoadingDOM树开始解析的时间此时Document.readyState是(变为?)loading,很多资料都写着,会触发监听document的readystatechange事件的回调,但实际执行的时候并没有触发
domInteractiveDOM树完成解析的时间此时Document.readyState是变为interactive,会触发监听document的readystatechange事件的回调,此时未开始加载资源
domContentLoadedEventStartDOM解析完毕后,网页内资源开始加载的时间发生在document的DOMContentLoaded事件触发前
domContentLoadedEventEndDOM解析完毕后,网页内资源(js阻塞脚本)加载完毕的时间发生在document的DOMContentLoaded事件执行完毕之后
domCompleteDOM加载完成的时间DOM 加载完成,包括页面上的所有资源(如图片、样式表、脚本等)都已经加载完毕,并且页面的布局和渲染也已经完成,但不代表所有资源的下载和显示都已完成,此时Document.readyState是变为complete,会触发监听document的readystatechange事件的回调
loadEventStart开始加载 load 事件的时间此时会准备开始执行document的load事件的回调,当loadEventStart时间点被触发时,意味着浏览器已经完成了页面及其所有相关资源的加载,页面处于完全可用的状态。
loadEventEndload 事件的回调执行完毕的时间load事件会在页面上所有的资源(包括图片、音频、视频等)加载完成后触发 

4a77ea80e5e1c576bc101915155492b7.jpg

补充点

Document.readyState

查询了一些资料,有说监听document的readystatechange事件,会在Document.readyState变为loading的时候执行一次回调函数,但实际上,写的demo发现并没触发,于是去查询了Document.readyState这个属性;

1.runoob网

  - readyState 属性返回当前文档的状态(载入中……)。

该属性返回以下值:

-   uninitialized - 还未开始载入
-   loading - 载入中
-   interactive - 已加载,文档与用户可以开始交互
-   complete - 载入完成

2.developer.mozilla.org

3.w3c

enum DocumentReadyState { "loading", "interactive", "complete" };

个人总结

  • 结合监听document的readystatechange事件回调里打印了Document.readyState,控制台并没有输出过loading,所以认为Document.readyState只有3个状态较为可信
    • loading:文档仍在加载中。
    • interactive:文档已被解析,"正在加载"状态结束,但是诸如图像、样式表和框架之类的子资源仍在加载。意味着可以开始访问 DOM 元素,但部分子资源尚未加载完成。DOMContentLoaded事件即将发生。
    • complete:文档和所有子资源已完成加载,表示load事件即将被触发。

资源加载的定义

通常所说的资源加载,DOM 结构的构建是其中的一部分。

资源加载一般涵盖了多种元素,例如 HTML 文档、CSS 样式表、JavaScript 脚本、图片、音频、视频等。

DOM(Document Object Model,文档对象模型)的构建是基于对 HTML 文档的解析。当浏览器获取到 HTML 代码后,会开始解析并构建 DOM 结构。

所以,从广义上讲,DOM 的构建可以被视为资源加载过程中的一个关键环节。但在讨论资源加载时,往往更侧重于外部引入的资源,如 CSS 文件、JS 文件、图像等,而 DOM 构建更多地被看作是页面初始化和渲染流程中的一个内部步骤。

domContentLoadedEventEnd(阻塞脚本)

  • 这里提及了是阻塞脚本,原因是因为加载html,遇到script标签的时候,不同模型有不同的加载方式,如使用 defer 或 async 属性的脚本
  • 关于script的加载方式,可以参考:

DOMContentLoaded事件

当以下两个条件都满足时,就会触发 DOMContentLoaded 事件:

  • 第一,浏览器已经成功地把 HTML 文档解析完,并构建好了页面的 DOM 结构。简单来说,就是页面的基本框架和内容在代码层面已经搭建好了。

  • 第二,那些会阻塞页面加载的脚本(也就是在 HTML 中直接编写,没有添加 defer 或者 async 属性的脚本)已经全部执行完了。

举个例子,如果一个网页的 HTML 中有一段普通的脚本(没有 defer 或 async),浏览器在解析 HTML 遇到这段脚本时,会停下来先执行这段脚本,只有执行完了,才会继续解析后面的 HTML 来构建 DOM。当 DOM 构建完成,并且这种会阻塞的脚本也都执行完了,就会触发 DOMContentLoaded 事件。

load事件

DOMContentLoaded 事件不同,DOMContentLoaded 只要求 DOM 构建完成且无阻塞脚本执行完毕,而 load 事件要等待所有资源完全加载。

  • 由于需要等待所有资源加载完成,load 事件可能会在页面加载过程中较晚触发。如果希望尽早执行某些操作,可能更适合使用 DOMContentLoaded 事件。
  • 加载并不完全等同于渲染,加载完毕只能说明是资源都下载(加载)完毕了

实际使用

通过减法组合不同的时间节点,可以定位不同阶段的耗时

let times = {};
let t = window.performance.timing;

// 优先使用 navigation v2  https://www.w3.org/TR/navigation-timing-2/
if (typeof window.PerformanceNavigationTiming === 'function') {
  try {
    var nt2Timing = performance.getEntriesByType('navigation')[0]
    if (nt2Timing) {
      t = nt2Timing
    }
  } catch (err) {
  }
}

//重定向时间
times.redirectTime = t.redirectEnd - t.redirectStart;

//dns查询耗时
times.dnsTime = t.domainLookupEnd - t.domainLookupStart;

//TTFB 读取页面第一个字节的时间
times.ttfbTime = t.responseStart - t.navigationStart;

//DNS 缓存时间
times.appcacheTime = t.domainLookupStart - t.fetchStart;

//卸载页面的时间
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;

//tcp连接耗时
times.tcpTime = t.connectEnd - t.connectStart;

//request请求耗时
times.reqTime = t.responseEnd - t.responseStart;

//解析dom树耗时
times.analysisTime = t.domComplete - t.domInteractive;

//白屏时间 
times.blankTime = (t.domInteractive || t.domLoading) - t.fetchStart;

//domReadyTime
times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;


参考资料:《10分钟彻底搞懂前端页面性能监控》[juejin.cn/post/684490…]

本文为个人整理,做了细节补充和额外拓展,如有错误,欢迎指正