页面性能优化最怕的不是“慢”,而是“不知道哪里慢”。很多开发者习惯凭感觉猜测:是不是图片太大了?是不是接口太慢了?是不是某个第三方脚本拖累了首屏?这些猜测往往耗时且低效。浏览器提供的 Performance API 正是为了解决这个问题——它把页面加载和运行过程中的关键节点、耗时数据、资源明细都暴露出来,让我们可以精确地定位瓶颈,而不是靠猜。
Performance API 能回答哪些问题
在动手写代码之前,先明确它可以帮我们回答的问题:
- 页面从请求到可交互,时间花在了哪些阶段?
- 哪个资源加载最慢、体积最大?
- 首屏渲染被什么阻塞了?
- 某个交互操作为什么响应迟钝?
- 长任务出现在什么时间点,持续了多久?
这些问题对应的核心数据来源包括 PerformanceNavigationTiming、PerformanceResourceTiming、PerformanceObserver 以及 performance.mark/measure。
从导航计时开始:拆解页面加载阶段
performance.getEntriesByType('navigation')[0] 返回一个 PerformanceNavigationTiming 对象,它把页面加载拆成了多个关键节点。常用的字段有:
redirectStart / redirectEnd:重定向耗时domainLookupStart / domainLookupEnd:DNS 查询耗时connectStart / connectEnd:TCP 连接耗时requestStart / responseStart:请求发出到首字节返回,即 TTFBresponseEnd:响应接收完成domInteractive:DOM 解析完成,可交互domContentLoadedEventEnd:DOMContentLoaded 事件结束loadEventEnd:load 事件结束
通过这些字段可以快速算出各阶段耗时:
const nav = performance.getEntriesByType('navigation')[0];
const dns = nav.domainLookupEnd - nav.domainLookupStart;
const tcp = nav.connectEnd - nav.connectStart;
const ttfb = nav.responseStart - nav.requestStart;
const download = nav.responseEnd - nav.responseStart;
const domParse = nav.domInteractive - nav.responseEnd;
const resourceLoad = nav.loadEventEnd - nav.domContentLoadedEventEnd;
console.table({ dns, tcp, ttfb, download, domParse, resourceLoad });
如果 TTFB 很高,问题通常出在服务端或网络链路;如果 download 很长,可能是响应体过大;如果 domParse 很长,说明 HTML 结构复杂或同步脚本过多;如果 resourceLoad 很长,则要重点看资源加载。
用 Resource Timing 找出最慢的资源
performance.getEntriesByType('resource') 会返回页面加载的所有资源条目,包括脚本、样式、图片、字体、XHR 等。每条记录包含 name、initiatorType、duration、transferSize、encodedBodySize 等字段。
一个实用的做法是按耗时排序,找出 Top 10 慢资源:
const resources = performance.getEntriesByType('resource');
const slowest = resources
.map(r => ({
name: r.name.split('/').pop(),
type: r.initiatorType,
duration: Math.round(r.duration),
size: r.transferSize,
}))
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
console.table(slowest);
这里要注意区分 duration 和 transferSize。duration 长不一定体积大,可能是排队或等待;transferSize 大则说明传输成本高。两者结合看,才能判断是“加载慢”还是“体积大”。
另外,如果资源来自跨域且没有设置 Timing-Allow-Origin 响应头,很多字段会返回 0,需要服务端配合开启。
用 PerformanceObserver 捕获长任务和布局偏移
前面的方法适合分析加载阶段,但页面运行时的卡顿需要另一套机制。PerformanceObserver 可以监听特定类型的性能条目,最常用的是 longtask 和 layout-shift。
长任务指执行时间超过 50ms 的任务,它会阻塞主线程,导致交互无响应:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn('长任务', {
start: Math.round(entry.startTime),
duration: Math.round(entry.duration),
attribution: entry.attribution,
});
}
});
observer.observe({ type: 'longtask', buffered: true });
通过 attribution 可以知道长任务来自哪个容器或脚本,从而定位到具体的代码块。布局偏移则用于监控 CLS:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('布局偏移', entry.value, entry.sources);
}
}
}).observe({ type: 'layout-shift', buffered: true });
entry.sources 会指出发生偏移的 DOM 元素,对修复 CLS 非常有帮助。
自定义测量:mark 和 measure
除了浏览器内置的指标,我们还可以手动标记关键业务节点。比如一个搜索请求从发起到渲染完成:
performance.mark('search-start');
fetch('/api/search')
.then(res => res.json())
.then(data => {
renderResults(data);
performance.mark('search-end');
performance.measure('search-duration', 'search-start', 'search-end');
const measure = performance.getEntriesByName('search-duration')[0];
console.log('搜索耗时', measure.duration);
});
这种方式适合监控特定交互或异步流程,把“用户感知的慢”转化为可量化的数字。
实战思路:从数据到结论
拿到数据后,建议按以下顺序排查:
- 先看导航计时,判断瓶颈在网络、服务端还是前端解析。
- 再看资源列表,找出体积大或耗时长的资源,优先处理阻塞渲染的 CSS 和 JS。
- 用长任务观察器检查运行时卡顿,定位到具体函数或第三方脚本。
- 用自定义 mark 验证优化效果,形成“测量—优化—再测量”的闭环。
需要提醒的是,Performance API 的数据受设备、网络、缓存状态影响很大。最好在真实用户环境(RUM)中采集,而不是只在本地开发机上测试。同时要注意数据采样率,避免上报本身成为性能负担。
小结
Performance API 的价值在于把模糊的“页面很慢”变成具体的阶段耗时、资源明细和任务时间线。它不需要额外引入庞大的监控库,浏览器原生支持,成本低、信息全。掌握导航计时、资源计时、PerformanceObserver 和自定义测量这四个工具,就足以覆盖大多数前端性能瓶颈的定位场景。真正重要的是养成用数据说话的习惯——先测量,再优化,最后用数据验证效果。
未经允许不得转载:任鹏个人博客 » 使用 Performance API 定位页面性能瓶颈


朋友圈点赞图在线生成源码