3、Navigation Timing:解析页面加载各阶段耗时
页面加载慢,到底慢在哪?
这是每个前端工程师都会遇到的问题。老板催你优化,你打开控制台一看,Network面板里一堆请求,红的绿的,但就是说不清具体哪里拖了后腿。
我个人习惯,第一件事就是打开 Navigation Timing API。它能把页面加载的整个过程拆成一个个阶段,像手术刀一样精准。DNS 查了多久?TCP 连了多久?TTFB 为什么那么高?一目了然。
3.1 Navigation Timing 是什么?
说白了,它就是浏览器提供的一组时间戳。从你输入 URL 按下回车,到页面完全渲染完毕,每个关键节点都被记录了下来。
这些数据藏在 window.performance.timing 对象里。你打开控制台,输入 performance.timing,就能看到一串数字。嗯,这些数字就是页面加载的“心电图”。
核心时间戳一览:
navigationStart:导航开始,用户按下回车的那一刻fetchStart:浏览器开始获取资源domainLookupStart / domainLookupEnd:DNS 查询开始和结束connectStart / connectEnd:TCP 连接建立requestStart:浏览器开始发送请求responseStart:浏览器收到第一个字节(TTFB 的关键)responseEnd:响应接收完毕domLoading / domInteractive / domContentLoadedEventEnd:DOM 解析相关loadEventEnd:页面完全加载完成
我记得刚入行时,以为页面加载就是“发请求-等响应-渲染”三步。后来看了这些时间戳才发现,光 DNS 查询就能吃掉几百毫秒,尤其是在移动端弱网环境下。
3.2 各阶段耗时计算
光看时间戳不够,你得算出差值。每个阶段的耗时,就是结束时间减去开始时间。
// DNS 查询耗时
const dnsTime = performance.timing.domainLookupEnd - performance.timing.domainLookupStart;
// TCP 连接耗时
const tcpTime = performance.timing.connectEnd - performance.timing.connectStart;
// TTFB(首字节时间)
const ttfb = performance.timing.responseStart - performance.timing.requestStart;
// 内容传输耗时
const contentDownloadTime = performance.timing.responseEnd - performance.timing.responseStart;
// DOM 解析耗时
const domParseTime = performance.timing.domInteractive - performance.timing.domLoading;
// 页面完全加载耗时
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
你想想看,这些数据拿到手,你就能精准定位问题。TTFB 高,说明后端或者网络有问题。DNS 慢,说明域名解析服务需要优化。DOM 解析慢,说明 HTML 结构太复杂或者 JS 阻塞了渲染。
我的一个小习惯:把这些数据上报到监控平台,按百分位统计。P50 和 P95 的差距,往往能暴露很多问题。比如 P50 的 TTFB 是 200ms,P95 却到了 2s,那说明有部分用户遇到了网络波动或后端抖动。
3.3 TTFB:首字节时间,用户体验的第一道坎
TTFB 是我最关注的指标之一。它衡量的是从浏览器发出请求,到收到服务器返回的第一个字节所花的时间。
为什么重要?因为用户等的是“看到东西”,而不是“下载完”。TTFB 越长,用户的白屏时间就越长。
我曾经踩过一个坑:有一次线上页面 TTFB 突然飙升到 3s 以上。查了半天,发现是后端某个接口在高峰期做了全表扫描,数据库 CPU 打满。前端再怎么优化也没用,最后还是后端加了缓存才解决。
所以,TTFB 高的时候,别光盯着前端。先确认一下是不是后端接口慢了。
TTFB 的组成通常包括:
- 网络延迟(RTT,往返时间)
- 服务器处理时间(后端逻辑、数据库查询)
- DNS 查询时间(如果没命中缓存)
- TLS 握手时间(如果是 HTTPS)
我个人建议,TTFB 的目标值:
- 优秀:小于 200ms
- 一般:200ms - 500ms
- 需要优化:大于 500ms
3.4 DNS 和 TCP:容易被忽略的“隐形杀手”
很多人觉得 DNS 和 TCP 是网络层的事,跟前端没关系。其实不然。
DNS 查询,说白了就是把你输入的域名翻译成 IP 地址。这个过程如果慢,用户连服务器都找不到。
TCP 连接,就是浏览器和服务器之间“握手”建立通道。三次握手,每次都要一个 RTT。
我记得有一次做海外站点优化,发现欧洲用户的 DNS 查询时间平均在 800ms 以上。后来换了支持 Anycast 的 DNS 服务商,直接降到了 100ms 以内。
优化建议:
- DNS 预解析:在 HTML 头部添加
<link rel="dns-prefetch" href="//example.com">,提前解析域名 - 预连接:使用
<link rel="preconnect" href="//example.com">,提前完成 TCP 和 TLS 握手 - 减少域名数量:每个域名都要一次 DNS 查询,能合并就合并
- 使用 CDN:CDN 节点离用户更近,能显著降低 RTT
3.5 实战:用代码采集并上报
光在控制台看没用,你得把这些数据采集下来,上报到监控平台。
function collectNavigationTiming() {
// 等待页面完全加载
window.addEventListener('load', () => {
// 使用 requestAnimationFrame 确保所有资源加载完毕
requestAnimationFrame(() => {
setTimeout(() => {
const timing = performance.timing;
const navigation = performance.navigation;
const metrics = {
// 各阶段耗时
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
download: timing.responseEnd - timing.responseStart,
domParse: timing.domInteractive - timing.domLoading,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
load: timing.loadEventEnd - timing.navigationStart,
// 页面信息
url: window.location.href,
type: navigation.type, // 0: 导航, 1: 刷新, 2: 前进/后退
timestamp: Date.now()
};
// 上报到监控平台
reportMetrics(metrics);
}, 0);
});
});
}
// 上报函数(示例)
function reportMetrics(data) {
// 使用 sendBeacon 确保页面卸载时也能发送
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/monitor', JSON.stringify(data));
} else {
// 降级方案:使用 Image 打点
const img = new Image();
img.src = `/api/monitor?data=${encodeURIComponent(JSON.stringify(data))}`;
}
}
collectNavigationTiming();
注意:Navigation Timing API 在页面加载完成后才能获取完整数据。所以要在 load 事件后采集。另外,performance.timing 已经被标记为废弃,推荐使用 PerformanceObserver 或者 performance.getEntriesByType('navigation')。不过原理是一样的。
3.6 新版 API:PerformanceNavigationTiming
嗯,这里要注意一下。老版的 performance.timing 虽然还能用,但 W3C 已经推荐使用新的 PerformanceNavigationTiming 接口了。
// 获取导航性能条目
const [navigationEntry] = performance.getEntriesByType('navigation');
if (navigationEntry) {
console.log('DNS 耗时:', navigationEntry.domainLookupEnd - navigationEntry.domainLookupStart);
console.log('TCP 耗时:', navigationEntry.connectEnd - navigationEntry.connectStart);
console.log('TTFB:', navigationEntry.responseStart - navigationEntry.requestStart);
console.log('DOM 交互时间:', navigationEntry.domInteractive);
console.log('页面完全加载时间:', navigationEntry.loadEventEnd);
}
新版的优势在于:
- 返回的是
DOMHighResTimeStamp,精度更高(微秒级) - 支持
PerformanceObserver,可以异步监听 - 数据更规范,更容易处理
3.7 总结一下
Navigation Timing 是性能监控的基石。它把页面加载拆成了 DNS、TCP、TTFB、DOM 解析等阶段,让你能精准定位瓶颈。
我个人建议,每个项目上线前,至少跑一遍这些指标。把数据上报到监控平台,持续跟踪。你会发现,很多性能问题其实早就埋下了,只是你没看到而已。
下一章,我们会深入 Resource Timing,看看如何监控单个资源的加载情况。到时候,连图片、脚本、样式表的加载细节都能抓出来。