先说结论:性能优化是证据链,不是技巧清单
资深工程师的性能能力,不是能背出多少压缩、缓存、懒加载和防抖技巧,而是能回答一条完整证据链:谁在什么设备和场景下遇到了什么问题,时间或资源消耗在哪里,哪项改动为什么可能有效,复测结果如何,以及怎样防止回归。
Vibe Coding 降低了运行 Lighthouse、生成分析脚本、比较 bundle 和尝试优化代码的成本,但没有降低错误归因的代价。AI 可以快速提出 memo、虚拟列表、Web Worker 或拆包方案,却无法只凭源码知道真实用户是否受影响、当前瓶颈是不是主因、性能预算来自什么业务目标,以及优化是否破坏正确性、无障碍和可维护性。
因此,性能工作的默认顺序是:先定义体验和场景,再测量;先找到主导瓶颈,再优化;最后用同条件复测和回归守卫证明结果。
三层掌握标准
| 层级 | 必须掌握或会用 | 验证方式 | AI 的位置 |
|---|---|---|---|
| 必须闭卷掌握 | 关键渲染路径的实用模型、主线程阻塞、网络与缓存、style/layout/paint、内存生命周期、测量先于优化 | 能从一个用户症状提出分层排查路径,并解释每项证据能排除什么 | 不能替代工程判断 |
| 会使用工具验证 | DevTools Performance/Network/Memory、Lighthouse、Web Vitals/RUM、bundle 分析、Vue/React Devtools | 保存可复现步骤、trace、请求瀑布、堆快照和前后数据 | 可帮助执行与摘要,原始证据必须可回看 |
| 可由 AI 初步完成 | 批量审计、trace/bundle 摘要、source map 热点报告、实验脚本、候选假设和报告初稿 | 人工抽查输入、工具版本、统计口径和结论,并独立复跑关键场景 | 加速机械工作,不负责最终因果结论 |
“会用 AI 做性能优化”至少意味着能审查它的测量环境、假设和改动范围。没有 trace、profile 或用户数据支持的优化建议,只能算待验证假设。
指标体系:从 Core Web Vitals 到业务体验
当前 Core Web Vitals 是 LCP、CLS 和 INP:
- LCP(Largest Contentful Paint)关注主要内容何时呈现,但“最大内容”是否等于用户真正等待的业务信息,需要结合页面确认。
- CLS(Cumulative Layout Shift)关注非预期布局偏移。主动交互后的合理变化与无提示跳动不能混为一谈。
- INP(Interaction to Next Paint)关注一次访问期间交互响应的整体表现。INP 已取代 FID 成为 Core Web Vital;FID 可能仍出现在历史看板中,但不应再当作当前核心指标。
通用参考常以第 75 百分位下 LCP 不高于 2.5 秒、CLS 不高于 0.1、INP 不高于 200 毫秒作为“良好”区间。这些值适合作为起点,不是所有产品的 SLA。项目预算还要写清用户分群、设备、网络、路由、操作流程、采样窗口和百分位。
| 想回答的问题 | 候选信号 | 主要限制 |
|---|---|---|
| 服务端或连接是否拖慢首屏 | TTFB、请求瀑布、缓存命中、Server-Timing | TTFB 正常不能证明资源加载和渲染正常 |
| 用户何时看到第一批内容 | FCP | 可能只是骨架或无业务价值的内容 |
| 主要内容何时出现 | LCP | 需要确认 LCP 元素与业务主内容一致 |
| 页面是否发生意外跳动 | CLS、Layout Shift 记录 | 分数不能直接解释是哪一处布局策略导致 |
| 交互是否迟钝 | Field INP、交互 trace、Event Timing | 必须结合具体交互、处理时间和下一帧呈现分析 |
| 初始加载期间主线程是否拥堵 | TBT、Long Task、Performance trace | TBT 是 Lab 中与阻塞相关的代理,不等同于 Field INP |
| 动画或 Web3D 是否流畅 | frame time、掉帧、CPU/GPU 时间、draw call | 平均 FPS 会掩盖长尾卡顿,也不能代表输入响应 |
| 页面是否随时间退化 | JS heap、DOM/监听器数量、GPU 资源、队列长度 | 内存上升可能是缓存或延迟回收,需要持续观测与对照 |
| 核心任务是否真的更快 | 搜索完成、设备上线、模型加载、命令确认等业务指标 | 必须定义起点、终点、失败和取消语义 |
不要把不同指标拼成一个“越低越好”的列表。指标首先服务于问题:首屏慢、交互慢、滚动卡、运行半小时后变慢,所需证据并不相同。
Field、Lab 与 Lighthouse 分别回答什么
Field / RUM:真实用户发生了什么
真实用户监控(RUM)能够按版本、路由、设备、网络和用户分群观察分布,回答问题是否普遍、影响谁、是否随发布变化。公开 CrUX 数据也属于 Field 数据来源,但覆盖粒度和样本条件未必等同于产品自己的 RUM。
Field 数据的限制是环境变量多、定位细节少。看到某个版本 INP p75 变差,只能说明需要调查,不能直接证明某个组件或函数是原因。
Lab:能否稳定复现和解释
Lab 测试固定构建、数据、设备与网络条件,适合录制 trace、检查请求瀑布、比较改动前后和做 CI 回归。初始加载型合成测试常用 TBT 观察主线程阻塞;针对真实交互还需要执行相同操作并查看 Event Timing 和渲染过程。
单次 Lab 结果容易受预热、缓存、后台进程、浏览器版本和网络噪声影响。重要结论应多次运行,保留分布或至少中位数,并确保前后条件一致。
Lighthouse:综合审计,不是最终裁判
Lighthouse 提供受控环境下的性能、无障碍和最佳实践审计,适合发现候选问题、建立基线和自动化比较。总分由多个指标和权重合成,会随版本和配置变化;一次高分不能证明真实用户的关键流程没有性能问题,一次低分也不能直接定位根因。
可靠的回归门槛应绑定明确场景和环境,例如固定页面、登录态、数据量、设备模拟与运行次数。Lighthouse 可以是其中一个信号,但不能替代 RUM、业务指标和 Performance trace。
一套可复用的性能诊断流程
1. 定义用户问题和操作路径
把“页面很卡”改写成可执行场景,例如“设备详情页首次进入时三维模型长时间空白”或“报警列表滚动时点击确认延迟明显”。记录入口、账号状态、数据量、操作步骤和期待结果。输出是一条任何人都能重复的用户旅程。
2. 固定实验条件
记录构建版本、浏览器版本、设备、窗口尺寸、网络与 CPU 模拟、缓存冷热、数据种子和后端状态。若比较前后版本时环境变了,差异可能来自噪声而不是代码。输出是测试配置和可重复命令。
3. 建立基线并保存原始证据
根据症状保存 Performance trace、Network HAR、Lighthouse 报告、堆快照、RUM 切片或业务时间戳。只在文档里写“优化前 3 秒”不够,后续无法确认测量口径。输出是基线数据与原始文件位置。
4. 找到主导等待或工作
优先解释最大的用户等待:服务端响应、关键资源、JavaScript Long Task、框架更新、布局绘制、GPU 提交还是队列积压。不要把工具列出的所有警告同时修一遍,否则无法判断哪项改动有效。输出是一个按证据排序的瓶颈列表。
5. 写出可证伪假设
例如:“点击后的 320 ms Long Task 主要来自同步解析 8 MB JSON;若把解析改为分块并减少无用字段,交互处理时间应明显下降,网络时间不变。”假设必须包含预期变化和可能反证。输出是一项最小实验,而不是一组泛化最佳实践。
6. 做最小相关改动
只修改足以验证假设的边界,并保留功能、错误路径和无障碍行为。不要顺手升级框架、改状态库和重写组件,否则变量过多。输出是可独立审查的 diff。
7. 同条件复测正确性与性能
重复相同场景和运行次数,比较分布、trace 和业务结果。平均值变好但长尾变差、报警丢失或交互语义改变,都不算成功。输出是前后对照与结论边界。
8. 建立回归守卫
重要收益进入性能预算、自动化场景或 RUM 告警。预算可以针对资源大小、Long Task、业务耗时、内存稳态或关键交互,不必全部压缩成 Lighthouse 总分。输出是失败条件、执行环境和负责人。
分层定位:网络、主线程、渲染与内存
| 层级 | 主要证据 | 常见原因 | 候选动作 | 主要代价 |
|---|---|---|---|---|
| 服务端、网络与缓存 | TTFB、HAR、Server-Timing、缓存头、连接复用 | 后端慢、串行请求、缓存失效、跨区网络 | 服务端优化、并行化、缓存/CDN、减少往返 | 一致性、失效策略、可观测性和运维成本 |
| 资源加载 | 请求优先级、coverage、bundle/source map、LCP 资源 | 首屏 JavaScript 过大、字体或图片阻塞、错误预加载 | 路由/组件拆分、压缩、图片尺寸、preload/preconnect | 请求数量、缓存碎片、占位与代码复杂度 |
| JavaScript 与主线程 | flame chart、Long Task、Event Timing、Bottom-Up | 同步解析、长循环、第三方脚本、过量序列化 | 分块、减少工作、调度、必要时 Worker | 数据复制、通信、取消和调试成本 |
| 框架更新与 DOM | Vue/React Profiler、组件更新原因、DOM 数量 | 高频状态发布、对象身份不稳、深响应、大列表 | 批量快照、稳定 props、浅状态、确认瓶颈后虚拟化 | 状态一致性、代码复杂度、可访问性 |
| style/layout/paint/composite | Rendering 面板、Layout Shift、Paint、图层 | 布局抖动、大面积绘制、非合成动画、DOM 读写交错 | 批量读写、减少影响范围、选择合成友好属性 | 图层内存、视觉取舍、浏览器差异 |
| 内存与生命周期 | Heap snapshot、allocation timeline、DOM/监听器/订阅计数 | 未清理监听、定时器、缓存、闭包、Detached DOM | 明确所有权、卸载清理、有界缓存、弱引用仅在合适语义下使用 | 清理时序、重新加载、缓存命中率 |
| Canvas/WebGL/GPU | frame time、Performance、renderer.info、draw call、GPU 工具 | 每帧分配、对象/材质过多、过绘制、资源未释放 | 复用、实例化、裁剪/LOD、按需渲染、dispose | 视觉质量、实现复杂度、GPU/驱动差异 |
一些常见结论必须带条件:
- HTTP/2 支持多路复用,不代表任意拆包都免费,也不代表应把所有代码重新合成一个包;仍要看缓存、优先级、解析和执行成本。
- Web Worker 可以移开主线程工作,但序列化、数据复制、调度、错误和取消都有成本。先确认主线程计算确实是瓶颈。
- 虚拟列表只减少大量列表项的 DOM/渲染成本,无法修复昂贵的数据解析、错误的响应式更新或 WebGL 掉帧。
memo、computed和缓存可能减少重复计算,也可能增加比较、内存和失效复杂度;必须用 profiler 证明命中场景。
工业状态看板与 Web3D 性能
工业状态看板通常同时包含高频数据、关键报警、长时间运行和三维场景。它不能套用“每条消息立即 setState”或“全部节流丢弃”的统一策略。
数据入口与 UI 发布分离
- WebSocket/ROS bridge 数据先做运行时校验、时间戳和设备 ID 归一化,再进入有界缓冲。
- 遥测位姿可以按设备保留最新值,UI 按固定节奏发布快照;人眼不需要逐条 DOM 更新。
- 报警、命令结果和审计事件不能与位姿采用同样的覆盖策略,需要可靠保留、确认或回放。
- 队列到达上限时必须有显式过载策略和指标,不能让内存无限增长。
框架层保持稳定边界
大型外部对象、Three.js 实例和只读快照不应默认进入深响应式代理。Vue 可在合适边界使用 shallowRef/markRaw,React 可用稳定引用和选择器减少无关更新;选择依据仍是 profiler,而不是框架偏好。
Web3D 控制每帧成本和资源生命周期
- 复用
Vector3、矩阵、BufferAttribute 等临时对象,避免热路径每帧制造大量垃圾。 - 共享或实例化重复的 geometry/material,结合视锥裁剪和 LOD 控制 draw call 与顶点/像素工作量。
- 静止场景可评估按需渲染;持续动画则应把数据采集、状态发布和渲染帧率解耦。
- Three.js 从场景移除对象通常不会自动释放 GPU 资源,确认所有权后调用 geometry、material、texture、render target 等对象的
dispose()。 - 使用
renderer.info和应用层资源计数观察趋势,但不要把单个计数当作跨设备统一 GPU 诊断结论。
示例验收合同
以下数字只是项目定义示例,不是行业标准:
1 | 场景:5,000 台模拟设备,每台每秒 10 条遥测,持续 30 分钟 |
测试还应保留固定输入或事件日志,能够回放一次卡顿和一次断连。没有可重复数据,现场性能问题很容易退化成“偶尔很卡”的主观争论。
Vibe Coding 时代怎样让 AI 参与性能工作
| AI 可以自动化 | 工程师必须决定 | 必须保留的证据 |
|---|---|---|
| 批量运行 Lighthouse、整理版本差异 | 哪条用户旅程值得优化、环境是否代表真实用户 | 配置、工具版本、原始报告和运行分布 |
| 汇总 trace、Long Task 和 source map 热点 | 这些热点是否是主因,是否值得改架构 | 原始 trace、调用栈、时间占比和复现步骤 |
| 比较 bundle、重复依赖和资源大小 | 拆分/移除对缓存、功能和团队维护的影响 | bundle 报告、加载瀑布和真实路由数据 |
| 生成 Playwright 场景、采样脚本和性能报告初稿 | 测试预言、预算、统计口径和发布门槛 | 可运行脚本、输入数据和失败样本 |
| 提出 memo、虚拟化、Worker、缓存等实验 | 因果假设、正确性、无障碍和复杂度取舍 | 预测指标、最小 diff、前后测量和反证 |
若 AI 只读源码就建议“加 memo + 虚拟列表 + Worker”,不要立刻实施。先要求它指出支持建议的 trace 片段、主线程占比、组件更新次数或 DOM 规模,并写出“若假设成立,哪个指标会变化”。没有证据时,把建议登记为候选实验即可。
适合交给智能体的闭环是:执行固定场景、收集工具产物、生成初步摘要、标出异常区段和制作对比表。人仍需回看原始数据、排除测量污染、确认业务正确性,并对最终取舍负责。
面试准备:讲诊断和取舍,不背固定答案
资深性能题可以统一使用:症状 -> 证据 -> 主导原因 -> 实验 -> 结果 -> 回归守卫。
| 面试场景 | 回答重点 | 不完整回答 |
|---|---|---|
| 首次加载很慢 | 分解 TTFB、关键资源、解析执行、LCP 元素和缓存;区分冷/热缓存与用户分群 | “上 CDN、gzip、懒加载” |
| INP 较差 | 找到具体交互,拆分输入延迟、处理和下一帧呈现,检查 Long Task 与更新范围 | “加防抖” |
| 滚动或动画卡顿 | 查看 frame time、主线程、layout/paint、对象分配和 GPU 工作 | “用 transform 就好” |
| 内存持续增长 | 固定操作循环,对比堆快照、Detached DOM、监听器、缓存和 GPU 资源趋势 | “调用 GC” |
| Web3D 掉帧 | 区分 CPU 更新、draw call、顶点/像素负载、纹理和资源抖动 | “降低模型面数” |
| Field 变差但 Lab 正常 | 分群设备/网络/路由/版本,补齐真实交互和长会话场景 | “Lighthouse 是绿的,所以没问题” |
| 如何证明优化有效 | 同条件多次运行、业务正确性、分布和回归门槛,并说明噪声与限制 | “分数从 80 到 95” |
面试时更有价值的是拿出一次失败实验:原以为 Worker 会改善交互,但数据复制抵消收益,最终通过减少输入规模解决。它证明你会证伪,而不是只会包装成功案例。
实践任务:工业状态看板性能治理
交付物
- 一份明确用户旅程、数据量、设备和网络条件的基线报告。
- 可回看的 Performance trace,以及一个网络假设和一个运行时假设。
- 两项最小实验、前后数据和至少一项未达到预期的实验记录。
- 资源、Long Task、业务耗时或内存稳态中的性能预算与回归命令。
- 一段五分钟面试复盘:症状、证据、取舍、结果和限制。
验收标准
- 优化前后使用同一构建方式、场景、数据和测量口径。
- 页面业务正确性、报警可靠性和无障碍行为没有被性能优化破坏。
- 报告链接到原始 trace/报告,不只保留截图和总结。
- 所有阈值标记为本项目预算,并说明设备、样本和百分位。
- 至少记录一个因为证据不足、收益过小或复杂度过高而放弃的优化。
面试价值
证明能够系统定位网络、主线程、框架、内存和 Web3D 问题,并用实验而不是术语数量推动决策。这类证据也能直接服务工业可视化和机器人调试台岗位。
性能预算和回归测试的测试层级见 前端测试策略与常用工具。
暂时不死磕什么
- 不背所有浏览器渲染流水线阶段和内部函数名,能从 trace 识别关键关系即可。
- 不记每个 Lighthouse 版本的指标权重和评分曲线,工具升级时查官方文档。
- 不把某个 Core Web Vitals 阈值、FPS 或 bundle 大小写成所有项目的“最佳值”。
- 不在没有 profile 的情况下死磕闭包、循环或语法层面的微优化。
- 不为了追求分数移除业务必要功能、无障碍语义、日志和错误处理。
- 不同时使用大量性能工具却没有一条可复现用户旅程。
自检清单
- [ ] 能区分 LCP、CLS、INP、TBT、Long Task、帧率、内存和业务指标解决的问题。
- [ ] 能解释 Field/RUM、Lab 和 Lighthouse 的证据边界。
- [ ] 能从用户症状建立固定场景,保存 trace,并写出可证伪假设。
- [ ] 能判断瓶颈主要在网络、主线程、框架更新、布局绘制、内存还是 WebGL。
- [ ] 能说明 Worker、虚拟化、缓存和 memo 何时可能无效或得不偿失。
- [ ] 能审查 AI 的输入、工具版本、原始证据、因果结论和代码取舍。
- [ ] 能拿出一组同条件的前后数据、一个失败实验和一条回归守卫。
- [ ] 能在面试中说明哪些数字来自真实用户、Lab、仿真或项目假设。
参考资料
- web.dev: Web Vitals
- web.dev: Interaction to Next Paint
- Chrome DevTools: Performance
- Chrome DevTools: Memory problems
- MDN: Performance APIs
- MDN: Server-Timing
- Three.js: How to dispose of objects
优先保留项目自己的用户数据、原始 trace 和复现脚本。工具文章和优化清单只能帮助提出假设,不能替代针对当前系统的证据。