本文主要叙述通过导航(navigation)和资源(resource)评估加载性能。
通过导航和资源评估加载性能
可以通过performance提供的api获取导航和资源计时
导航和资源计时之间存在明显的重复覆盖,但它们各自收集不同方面的指标:
- 导航(navigation)计时收集HTML文档的性能指标。
- 资源(resource)计时收集文档相关资源的性能指标。东西像样式表,脚本,图片,等等。
(导航)PerformanceNavigationTiming接口定义的计时演示如下:

(资源)PerformanceResourceTiming接口定义的计时演示如下:

导航和资源计时(以及其他相关API)将性能数据存储在缓冲区中。它们将页面和资源的性能指标存储到JavaScript可访问的列表中。这些方法存在于window.performance名称空间中。我们可以使用getEntriesByType进行访问。
// 获取导航计时项:
performance.getEntriesByType("navigation");
// 获取资源计时项:
performance.getEntriesByType("resource");
获取的结果如下:
{
"connectEnd": 152.20000001136214,
"connectStart": 85.00000007916242,
"decodedBodySize": 1270,
"domComplete": 377.90000007953495,
"domContentLoadedEventEnd": 236.4000000525266,
"domContentLoadedEventStart": 236.4000000525266,
"domInteractive": 236.2999999895692,
"domainLookupEnd": 85.00000007916242,
"domainLookupStart": 64.4000000320375,
"duration": 377.90000007953495,
"encodedBodySize": 606,
"entryType": "navigation",
"fetchStart": 61.600000015459955,
"initiatorType": "navigation",
"loadEventEnd": 377.90000007953495,
"loadEventStart": 377.90000007953495,
"name": "https://example.com/",
"nextHopProtocol": "h2",
"redirectCount": 0,
"redirectEnd": 0,
"redirectStart": 0,
"requestStart": 152.50000008381903,
"responseEnd": 197.80000008177012,
"responseStart": 170.00000004190952,
"secureConnectionStart": 105.80000001937151,
"startTime": 0,
"transferSize": 789,
"type": "navigate",
"unloadEventEnd": 0,
"unloadEventStart": 0,
"workerStart": 0
}
Network中的瀑布流显示了其信息


接下来分阶段描述各阶段情况:
DNS查询
当用户请求URL时,将查询域名系统(DNS)以将域转换为IP地址。由于许多因素(尤其是DNS缓存)影响,此过程可能会花费大量时间。导航和资源计时都公开了两个与DNS相关的指标:
- domainLookupStart DNS查找开始时标记。
- domainLookupEnd DNS查找结束时标记。
衡量dns查询时间
var pageNav = performance.getEntriesByType("navigation")[0];
var dnsTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
但是注意:跨域资源是否可访问该数据还受到http请求头影响,响应头Timing-Allow-Origin用于指定特定站点,以允许其访问Resource Timing API提供的相关信息,否则这些信息会由于跨源限制将被设置为零。
连接协商
与服务器建立连接后,在客户端请求服务器和服务器将资源发送给客户端之前,客户端和服务器会进行处理,从而发生延迟。如果使用HTTPS(这越来越普遍),则此过程还包括TLS协商时间。连接阶段包含三个指标:
- connectStart 客户端打开与服务器的连接时标记。
- secureConnectionStart 在客户端开始TLS协商时标记。
- connectEnd 在连接协商结束时标记(包括TLS时间)。
这些几乎和DNS指标一样,但是secureConnectionStart有些不同,为什么会没有"secureConnectionEnd",因为TLS结束和connectEnd是同时的。非https下secureConnectionStart为0。
// 查询总的连接时间
var pageNav = performance.getEntriesByType("navigation")[0];
var connectionTime = pageNav.connectEnd - pageNav.connectStart;
// 查询tls连接时间
var tlsTime = 0;
if (pageNav.secureConnectionStart > 0) {
tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}
请求和响应
考虑影响页面速度的因素时,我们应该考虑两个因素:
- 外在因素:这是诸如连接延迟和带宽之类的东西。他们(大部分)是我们无法控制的。
- 内在因素:这些是我们可以更好地控制的事情,例如服务器和客户端架构以及资源大小。
两种类型的因素都会影响请求和响应速度。导航和资源计时都使用以下方式衡量请求和响应:
- fetchStart 当浏览器开始获取资源时标记。这与请求不同,因为它不标记浏览器何时发出网络请求,而是标记它何时开始检查缓存(例如HTTP和Service-worker缓存)从而确定是否需要网络请求。
- workerStart 在fetch事件处理程序从开始service-worker获取请求时标记。如果当前页面未安装service-worker,则此标记为0。
- requestStart 当浏览器发出网络请求时。
- responseStart 当响应的第一个字节到达时。
- responseEnd 当响应的最后一个字节到达时。
可以使用这些指标来衡量加载性能的许多方面。例如,可以衡量资源下载时间的同时计算缓存查找时间:
// 缓存查找加上响应时间
var pageNav = performance.getEntriesByType("navigation")[0];
var fetchTime = pageNav.responseEnd - pageNav.fetchStart;
// service-worker响应时间
var workerTime = 0;
if (pageNav.workerStart > 0) {
workerTime = pageNav.responseEnd - pageNav.workerStart;
}
还可以测量其他的时间:
// 单独请求的时间
var requestTime = pageNav.responseStart - pageNav.requestStart;
// 单独响应的时间
var responseTime = pageNav.responseEnd - pageNav.responseStart;
// 请求开始到响应结束的时间
var requestResponseTime = pageNav.responseEnd - pageNav.requestStart;
页面卸载
卸载是指浏览器在加载新页面之前进行一些整理工作。大多数情况下不需要关注。但是如果有一些代码在unload事件中处理,则需要考虑unloadEventStart和unloadEventEnd。
注意:卸载计时是"navigation timing"所独有的。
重定向
重定向会增加请求的延迟,因此有时也需要对重定向进行测量。可以使用API都提供 redirectStart 和 redirectEnd 指标。
页面处理
可以通过domInteractive(可交互), domContentLoadedEventStart(dom加载开始), domContentLoadedEventEnd(dom加载完成),和 domComplete(文档准备就绪)来进行计算。
如图红色区域所示:

载入中
当文档及其资源完全完成加载后,浏览器将触发一个load event。loadEventStart 并且 loadEventEnd 可以测量加载时间。
var pageNav = performance.getEntriesByType("navigation")[0];
var loadTime = pageNav.loadEventEnd - pageNav.loadEventStart;
文件和资源大小
文档或资源的大小无疑对加载性能有影响。我们可以通过以下api度量:
- transferSize是资源的总大小,包括HTTP标头。
- encodedBodySize是资源的压缩大小,不包括 HTTP标头。
- decodedBodySize是资源的解压缩大小,不包括HTTP标头。
计算响应的http头部大小和压缩率:
// HTTP 头部大小
var pageNav = performance.getEntriesByType("navigation")[0];
var headerSize = pageNav.transferSize - pageNav.encodedBodySize;
// 压缩率ression ratio
var compressionRatio = pageNav.decodedBodySize / pageNav.encodedBodySize;
手动获取时间
-
getEntriesByName
getEntriesByName 通过名称获取性能列表。对于导航和资源衡量来说,需要传入导航或资源的url:
var imageTime = performance.getEntriesByName("https://somesite.com/images/some-image.jpg"); -
getEntries
与getEntriesByName和getEntriesByType不同,getEntries可以获取到所有的性能列表。
var allTheTimings = performance.getEntries();
使用PerformanceObserver监听
例如getEntriesByType这种获取方式,需要循环来使用他们,可能会造成如下问题:
- 循环大型数组,会阻塞主线程
- 只能获取循环运行时刻的性能列表,需要计时器定期轮询,会导致与渲染器竞争。
PerformanceObserver提供了类似Mutation或Intersection Observer的观察者模式,可以提供一个回调。
// 初始化Observer
var perfObserver = new PerformanceObserver(function(list, obj) {
// 可以获取所有的列表
// 也可以使用getEntriesByType/getEntriesByName
var entries = list.getEntries();
for (var i = 0; i < entries.length; i++) {
// 做些事情!
}
});
// 开始观测
perfObserver.observe({
entryTypes: ["navigation", "resource"]
});
注意:IE下暂不支持PerformanceObserver,使用前先检查此功能:
if ("performance" in window) {
if ("PerformanceObserver" in window) {
} else {
}
}
注意事项
- 持久连接会影响计时 使用Connection:Keep-Alive或http2时,使用单个连接传送所有资源可能会对计时产生影响。
- 浏览器兼容性 部分浏览器支持不完善,使用前需要检查
上报
可以使用navigator.sendBeacon进行数据上报
let rumData = JSON.stringify(performance.getEntries()));
if ('sendBeacon' in navigator) {
if (navigator.sendBeacon('/analytics', rumData)) {
} else {
// 使用xhr或者fetch代替
}
} else {
// sendBeacon 不可用 使用xhr或者fetch代替
}