前言
在前端开发中,"性能"是用户体验的生命线。但性能不能仅凭感觉,需要精确的数据支撑。现代浏览器提供了强大的 performance 接口,让我们能够精准捕捉从 DNS 解析到页面完全加载的每一个瞬间。
一、 性能监控的基石:Performance API
performance 对象允许我们访问当前页面的性能相关信息。它提供的指标比传统的 Date.now() 精确得多(可达微秒级)。
1. getEntriesByType() 方法
这是性能监控中最常用的 API,用于获取特定类型的性能数据。它接受一个字符串作为参数,返回一个包含性能对象的数组。
-
当你传入
'navigation'时: 你告诉浏览器:“给我看整张网页从输入 URL 到加载完成的报告。”- 返回内容:通常数组里只有一个对象(即当前页面的导航信息)。
- 包含属性:
domInteractive,domComplete,loadEventEnd等。
-
当你传入
'resource'时: 你告诉浏览器:“给我看页面里每一个零件(图片、脚本、样式表)的进场报告。”- 返回内容:一个包含多个对象的数组,页面上有多少个外部资源,数组就有多长。
- 包含属性:
name(资源的 URL),initiatorType(img/script),transferSize等。
二、 核心性能指标计算
通过 performance.getEntriesByType 拿到的对象包含了大量时间戳,我们需要通过差值来计算出有意义的指标。
1. 页面加载全时长 (Load Time)
衡量用户从发起请求到页面完全加载完成的总时间。
const [nav] = performance.getEntriesByType('navigation');
// 比较准确的总加载时长:从导航开始到 load 事件完成
const loadTime = nav.loadEventEnd - nav.startTime;
console.log(`页面完全加载耗时: ${loadTime.toFixed(2)}ms`);
2. 关键阶段耗时
除了总时长,我们通常更关注某个环节是否缓慢:
| 指标 | 计算方式 | 含义 |
|---|---|---|
| DNS 解析耗时 | domainLookupEnd - domainLookupStart | 域名解析是否迅速 |
| TCP 连接耗时 | connectEnd - connectStart | 网络连接建立时间 |
| 首字节时间 (TTFB) | responseStart - startTime | 服务器响应速度的关键指标 |
| DOM 解析耗时 | domComplete - responseEnd | HTML 结构解析的复杂度 |
三、 资源加载性能监控
每个静态资源(图片、脚本等)的加载同样影响性能。我们可以通过 resource 类型来监控它们。
1. 单个资源加载耗时
const resources = performance.getEntriesByType('resource');
resources.forEach(res => {
// 计算资源从发起请求到接收完成的时间
const duration = res.responseEnd - res.requestStart;
if (duration > 1000) {
console.warn(`资源加载过慢 (${res.name}): ${duration.toFixed(2)}ms`);
}
});
四、 进阶:性能监控的“三座大山” (Core Web Vitals)
现代性能监控不再只看 Load 事件,Google 提出的 Core Web Vitals 才是重点。虽然 getEntriesByType 能拿到基础数据,但通常推荐使用 PerformanceObserver 来监听:
-
LCP (Largest Contentful Paint) :最大内容绘制,衡量视觉加载速度。
const lcpObserver = new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); const lastEntry = entries[entries.length - 1]; // 取当前最大的绘制元素 console.log('LCP 耗时:', lastEntry.startTime); }); // buffered: true 表示即使事件发生在监听之前,也要拉取数据 lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true }); -
FID (First Input Delay) :首次输入延迟,衡量交互响应速度。
const fidObserver = new PerformanceObserver((entryList) => { entryList.getEntries().forEach((entry) => { // 延迟时间 = 处理开始时间 - 输入开始时间 const delay = entry.processingStart - entry.startTime; console.log('FID 延迟:', delay); }); }); fidObserver.observe({ type: 'first-input', buffered: true }); -
CLS (Cumulative Layout Shift) :累计布局偏移,衡量视觉稳定性。
let clsValue = 0; const clsObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { // 如果该偏移没有最近的用户输入(说明是非预期的),则累加 if (!entry.hadRecentInput) { clsValue += entry.value; console.log('当前累积 CLS:', clsValue); } } }); clsObserver.observe({ type: 'layout-shift', buffered: true });
五、 实战建议与避坑
- 精度问题:
performance返回的是高精度时间戳,建议使用.toFixed(2)格式化。 - 时机问题:计算
loadEventEnd时,必须确保在window.onload之后执行,否则该值为 0。 - 跨域限制:如果监控的静态资源是 CDN 上的跨域资源,需要在响应头加上
Timing-Allow-Origin: *,否则你只能拿到startTime和duration,中间的细节指标(如 DNS、TCP)将全部为 0。