Back

如何测量 JavaScript 性能

如何测量 JavaScript 性能

JavaScript 性能问题很容易感知,但很难精确定位。你的应用感觉迟缓,交互有延迟,用户会注意到——但如果没有合适的测量工具,你只能靠猜测。本文介绍使用现代浏览器 API 和 DevTools 进行实用的 JavaScript 性能测量技术,帮助你找到真正的瓶颈并修复正确的问题。

核心要点

  • 区分合成(实验室)测试和真实用户监控(RUM)——使用实验室工具进行诊断,使用现场数据进行验证。
  • Chrome DevTools 的 Performance 面板可显示长任务、火焰图和调用树,用于实际性能分析。
  • Performance API(performance.now()performance.mark()performance.measure())提供精确的程序化计时,可与 DevTools 集成。
  • PerformanceObserver 可自动收集条目用于生产环境监控,包括长任务检测。
  • Interaction to Next Paint(INP)是与响应性最密切相关的 Core Web Vital 指标。JavaScript 执行是主要影响因素,同时还包括样式、布局和绘制工作。

实验室测试 vs. 真实用户数据

在使用工具之前,需要了解 JavaScript 性能测量的两种类型:

  • 合成(实验室)测试在受控环境中运行你的代码。像 Lighthouse 和 Chrome DevTools 这样的工具可以提供可重复、可调试的结果。非常适合开发和 CI 流水线。
  • **现场数据(RUM)**捕获真实用户的体验。像 Chrome User Experience Report (CrUX) 或 RUM 平台这样的工具可以显示跨设备和网络的实际性能。

使用实验室工具诊断问题。使用现场数据确认问题的重要性。

在 Chrome DevTools 中分析 JavaScript

Chrome DevTools 是 JavaScript 性能指标测量最实用的起点。打开 Performance 面板,点击录制,与页面交互,然后停止。

需要关注的内容:

  • 长任务(Long Tasks)——任何阻塞主线程超过 50ms 的任务会以红色显示。这些通常会延迟用户交互。现代工具还可能显示长动画帧(Long Animation Frames),它提供影响响应性的慢帧的更详细信息。
  • 调用树/自底向上视图(Call Tree / Bottom-Up views)——识别哪些函数消耗了最多的执行时间。
  • 火焰图(Flame chart)——可视化随时间变化的调用堆栈,以发现昂贵的同步工作。

Firefox DevTools 提供类似的分析器。这两个工具都是免费的,无需设置,适用于任何网站。

使用 Performance API 测量 JavaScript 执行时间

对于精确的程序化 JavaScript 性能测量,使用浏览器内置的 Performance API

使用 performance.now()

performance.now() 返回以毫秒为单位的高分辨率时间戳,相对于页面的时间原点——这使其比 Date.now() 更可靠地用于代码计时。

const start = performance.now()
runExpensiveOperation()
const duration = performance.now() - start
console.log(`Took ${duration}ms`)

使用 performance.mark()performance.measure()

对于跨多个点的结构化计时,使用标记和测量。这可以直接与 DevTools 和 PerformanceObserver 集成。

performance.mark('fetch-start')
const data = await fetchData()
performance.mark('fetch-end')

const measure = performance.measure('fetch-duration', 'fetch-start', 'fetch-end')
console.log(measure.duration) // 毫秒

测量结果会出现在 Chrome DevTools Performance 面板的 Timings 轨道下,便于与主线程上的其他活动关联。

使用 PerformanceObserver 自动化测量

PerformanceObserver 允许你在性能条目发生时做出响应——对生产环境监控很有用。

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.duration)
  }
})

observer.observe({ type: 'measure', buffered: true })

你还可以观察 longtask 条目,以检测真实用户会话中的主线程阻塞。

Core Web Vitals 和 INP:重要的指标

Google 的 Core Web Vitals 是用户体验的标准 JavaScript 性能指标。与 JavaScript 最相关的是 Interaction to Next Paint(INP)——它测量页面生命周期内所有交互(点击、轻触、键盘输入)的延迟。

INP 超过 200ms 是一个警告信号。超过 500ms 则表示性能较差。事件处理程序期间的大量 JavaScript 执行是最常见的原因。

使用 web-vitals 库在现场测量 INP:

import { onINP } from 'web-vitals'
onINP(({ value }) => console.log('INP:', value))

对于单页应用(SPA),请注意软导航(无需完整页面加载的路由更改)仅被标准导航指标部分捕获。浏览器对软导航测量的支持仍在发展中,因此使用 performance.mark() 手动检测路由转换可以帮助填补空白。

选择合适的工具

目标工具
快速调试console.time() / console.timeEnd()
精确计时performance.now()
结构化、可视化计时performance.mark() + performance.measure()
自动化监控PerformanceObserver
完整页面分析Chrome DevTools Performance 面板
审计评分 + 现场数据Lighthouse + CrUX

结论

有效的 JavaScript 性能测量始于为正确的问题选择合适的工具。使用 DevTools 进行分析和探索,使用 Performance API 检测特定代码路径,使用 Core Web Vitals——尤其是 INP——来了解用户的实际体验。先测量,再优化。

常见问题

performance.now() 返回相对于页面时间原点的高分辨率时间戳,提供亚毫秒级精度。Date.now() 依赖系统时钟,可能受到时钟调整的影响,仅提供毫秒级分辨率。对于代码执行基准测试,performance.now() 是更准确和可靠的选择。

使用配置为观察 longtask 类型条目的 PerformanceObserver。任何在主线程上超过 50ms 的任务都会被标记为长任务。通过在生产环境中收集这些条目,你可以识别哪些用户交互触发了阻塞工作,并优先优化最重要的部分。

Interaction to Next Paint 测量页面访问期间所有交互的延迟,并报告最差的一次。First Input Delay 仅捕获第一次交互的延迟。INP 提供了运行时响应性的更全面的视图,这就是为什么 Google 在 2024 年 3 月用 INP 替换 FID 作为 Core Web Vital 的原因。

可以。在 await 表达式之前和之后放置 performance.mark() 调用,然后使用两个标记名称调用 performance.measure() 来计算经过的时间。生成的测量条目包括异步操作的完整持续时间,并出现在 DevTools Timings 轨道中以便进行可视化关联。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay