← 返回文章列表
浏览器与网络·627 次阅读

Performance API:别再拿 Date.now() 测性能了

最近排查一个页面交互卡顿,同事的分析报告里写着 Date.now() - startTime。我当场给他安利了 Performance API——都 2024 年了,性能测量该用专业工具。

Date.now() 的两个硬伤

const start = Date.now()
doSomething()
console.log('耗时:', Date.now() - start, 'ms')

这个写法的问题:

  1. 精度只有毫秒,且最小刻度实际可能到 4ms(浏览器节流)。测一个 0.5ms 的操作毫无意义
  2. 受系统时钟影响:用户校时、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 条

还没有评论,来抢沙发~

发表评论

评论需审核后显示

© 2026 许冬博客 · 专注 JS 技术分享

·RSS 订阅·Powered by Nuxt 4