Performance API:别再拿 Date.now() 测性能了
最近排查一个页面交互卡顿,同事的分析报告里写着 Date.now() - startTime。我当场给他安利了 Performance API——都 2024 年了,性能测量该用专业工具。
Date.now() 的两个硬伤
const start = Date.now()
doSomething()
console.log('耗时:', Date.now() - start, 'ms')
这个写法的问题:
- 精度只有毫秒,且最小刻度实际可能到 4ms(浏览器节流)。测一个 0.5ms 的操作毫无意义
- 受系统时钟影响:用户校时、NTP 同步都会让 Date.now() 跳变,甚至可能算出负数
performance.now() 是单调递增的高精度时间戳(微秒级),从页面加载开始计,不受系统时钟干扰:
const start = performance.now()
doSomething()
console.log('耗时:', performance.now() - start, 'ms') // 精确到小数点后三位
mark + measure:标准化打点
散落的 performance.now() 减法多了也不好管理。标准姿势是打点(mark)+ 测量(measure):
performance.mark('fetch-start')
await fetchUserData()
performance.mark('fetch-end')
performance.mark('render-start')
renderUserList()
performance.mark('render-end')
// 测量两段之间的时长
performance.measure('数据请求', 'fetch-start', 'fetch-end')
performance.measure('列表渲染', 'render-start', 'render-end')
// 读取结果
const measures = performance.getEntriesByType('measure')
measures.forEach((m) => {
console.log(`${m.name}: ${m.duration.toFixed(2)}ms`)
})
好处是打点数据进了浏览器的 Performance Timeline,Chrome DevTools 的 Performance 面板录制时会把 measure 显示成可视化的区间,不用自己拼日志。
PerformanceObserver:自动收集
手动打点适合特定分析,常态监控用 PerformanceObserver 订阅各类性能条目:
// 监听所有 measure
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
reportToServer({ name: entry.name, duration: entry.duration })
}
})
observer.observe({ entryTypes: ['measure'] })
可订阅的类型很多,常用的:
'navigation':页面加载各阶段(DNS、TCP、TTFB、DOM 解析)'resource':每个静态资源的加载耗时'paint':FP、FCP'largest-contentful-paint':LCP'longtask':超过 50ms 的长任务(卡顿监控的核心)
实战:页面加载耗时拆解
Navigation Timing 把页面加载拆成了标准化阶段,一行拿到全部数据:
const [nav] = performance.getEntriesByType('navigation')
console.table({
'DNS 查询': nav.domainLookupEnd - nav.domainLookupStart,
'TCP 连接': nav.connectEnd - nav.connectStart,
'首字节 TTFB': nav.responseStart - nav.requestStart,
'HTML 下载': nav.responseEnd - nav.responseStart,
'DOM 解析': nav.domContentLoadedEventEnd - nav.responseEnd,
'资源加载': nav.loadEventStart - nav.domContentLoadedEventEnd,
'总耗时': nav.loadEventEnd - nav.startTime,
})
页面上报这段数据,就能在监控后台回答"用户打开慢,到底慢在哪一段"——是网络问题(DNS/TCP/TTFB)还是前端问题(DOM 解析/资源加载),一目了然。
实战:长任务监控
页面卡顿的元凶是长任务(Long Task,主线程被霸占超过 50ms)。监控它:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entry.duration > 50ms
reportToServer({
type: 'longtask',
duration: entry.duration,
startTime: entry.startTime,
})
}
})
observer.observe({ entryTypes: ['longtask'] })
线上收集一周,哪个页面长任务密集、出现在什么时机,数据自己会说话。我们曾靠这个定位到"某第三方 SDK 初始化阻塞主线程 800ms"的隐蔽问题。
内存占用观察(Chrome 限定)
// 非标准 API,仅 Chrome
if (performance.memory) {
console.log({
'已用堆内存': (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB',
'堆内存上限': (performance.memory.jsHeapSizeLimit / 1048576).toFixed(1) + 'MB',
})
}
排查内存泄漏时,定时采样 usedJSHeapSize 看趋势:只涨不回落就是泄漏信号,再配合 DevTools 的 Memory 面板抓堆快照。
清理:别忘了 clear
打点是会累积的,单页应用跑久了 timeline 里全是历史数据,定期清理:
performance.clearMarks() // 清空所有 mark
performance.clearMeasures() // 清空所有 measure
// 或按名字清:performance.clearMarks('fetch-start')
小结
- 性能测量用
performance.now(),弃用 Date.now() - 打点测量标准化:mark 打区间、measure 算时长、DevTools 里可视化
- PerformanceObserver 订阅 navigation/resource/longtask 做常态监控
- Navigation Timing 一行拆解页面加载全阶段,定位"慢在哪"
- 长任务监控是线上卡顿排查的利器
性能优化的一半是测量。把这套 API 接进监控平台,"感觉最近有点卡"就会变成"长任务 P95 上升了 120ms,集中在商品页"——前者只能焦虑,后者才能行动。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~