Skip to main content
性能优化说到底是用数据找瓶颈、再验证收益,不是先背一堆方案。

Q1: Core Web Vitals 是什么?为什么重要?

1

指标体系概述:从碎片化到统一标准

早期浏览器性能指标有十几个(FCP、TTI、TBT、FID、LCP、CLS 等),开发者不知道该关注哪个。2020 年 Google 提出 Core Web Vitals,将性能指标精简为三个维度:加载性能(LCP)、交互响应(INP)、视觉稳定性(CLS)。除了这三个核心指标,FCP(First Contentful Paint,首次内容绘制)也是一个重要的辅助指标——它衡量用户何时看到第一个有意义的内容,是感知加载速度的起点。
2

与 SEO 的关系:直接影响搜索排名

Google 从 2021 年起将 Core Web Vitals 作为搜索排名因子。这意味着 LCP、INP、CLS 不仅是用户体验指标,更是直接影响网站流量的 SEO 指标。PageSpeed Insights 和 Search Console 都会展示这些指标的达标情况。
3

Chrome DevTools Performance 面板怎么用

在 Chrome DevTools 中,Performance 面板是分析 Core Web Vitals 的核心工具。录制一段页面加载或用户操作后,面板会展示 FPS(帧率)、Main(主线程任务)、Network(网络请求瀑布图)等关键信息。LCP 会以竖线标记在时间轴上,Layout Shift 会在 Experience 行中高亮显示。
Core Web Vitals 解决的核心问题:
Core Web Vitals 把性能优化从”指标堆砌”简化为”三个核心维度、一组阈值”,让优化有明确的目标,且直接影响 SEO 排名。

没有 Core Web Vitals

就像一个学生不知道考试考什么,语文数学英语物理化学都要复习,精力分散且没有重点。优化了 FCP 但 LCP 变差了,优化了 TTI 但 CLS 变差了,顾此失彼。

引入 Core Web Vitals

就像考试大纲明确告诉你核心只考三门,每门有明确的及格线。你只需要集中精力把 LCP、INP、CLS 考好(≤ 2.5s、≤ 200ms、≤ 0.1),再顺手关注 FCP 这个辅助指标。目标清晰,优化有方向。
回答时先说三个核心指标分别衡量什么(加载/交互/稳定),再提 FCP 作为辅助,最后说它们与 SEO 的关系。想听的是你对指标背后设计意图的理解——它们直接对应用户体验的几个维度,而不是 Google 随便选的几个数字。

Q2: FCP(First Contentful Paint)怎么优化?

FCP(First Contentful Paint,首次内容绘制)衡量从页面开始加载到浏览器渲染出第一个 DOM 内容(文本、图片、SVG、非空白 Canvas)的时间。它是用户感知”页面开始有东西了”的起点。

定义与阈值

采集方式

分析方法

在 Chrome DevTools 的 Performance 面板中,FCP 会以绿色竖线标记在时间轴上。点击 Lighthouse 的 Performance 审计,可以看到 FCP 的细分时间:TTFB(服务器响应时间)、DOM 解析时间、关键资源加载时间。

优化策略


Q3: LCP(Largest Contentful Paint)怎么优化?

LCP(Largest Contentful Paint,最大内容绘制)衡量页面主要内容的加载速度,本质是从导航开始到最大内容元素渲染完成的时间。

定义与阈值

核心优化逻辑只有 4 步:

采集方式

分析方法

Lighthouse 会自动识别 LCP 元素并给出优化建议。Chrome DevTools 的 Performance 面板中,LCP 会以蓝色竖线标记。点击该标记可以看到 LCP 元素的具体信息(类型、URL、尺寸)。

优化策略

LCP 的候选元素类型有严格定义:<img /> 标签、<svg> 中的 <image>、<video> 的封面图、通过 background-image 加载的元素、包含文本节点的块级元素。最常见的 LCP 瓶颈是图片发现时机——图片在 CSS background 中(浏览器要先解析 CSS 才能发现)、被 JS 动态创建(浏览器要先执行 JS)、或者被 loading="lazy" 误伤(首屏图片不应该 lazy load)。
LCP 就像网购到货的速度。优化 LCP 不仅仅是把包裹压得更小(压缩图片),更重要的是让仓库尽早发现你要的货(资源发现时机)。如果你下单后仓库 3 小时才发现你的订单(浏览器 3 秒后才发现首屏图片),即使快递再快(图片已经压缩到最小),到货时间也不会快。所以 preload 就像”预售提醒”——在你正式下单前,仓库就已经开始备货了。

LCP 的候选元素详解


Q4: CLS(Cumulative Layout Shift)怎么优化?

CLS(Cumulative Layout Shift,累积布局偏移)衡量页面加载过程中元素发生意外位移的程度。它是唯一一个”越小越好”的指标——0 表示没有布局偏移。

定义与阈值

CLS 的计算公式:

采集方式

分析方法

Chrome DevTools 的 Performance 面板中,Layout Shift 会在 Experience 行中以红色方块高亮显示。点击方块可以看到具体的偏移元素和偏移距离。Lighthouse 也会列出所有 CLS 来源。

优化策略

CLS 使用 Session Window 算法:5 秒内连续发生的布局偏移算一个 session,取所有 session 中的最大值。这意味着:
  • 短时间内多次小偏移会被合并计算
  • 超过 5 秒间隔的偏移会被分开计算
  • 用户交互(点击、滚动)引起的偏移不计入 CLS

Q5: INP(Interaction to Next Paint)怎么优化?

INP(Interaction to Next Paint,交互到下一帧绘制)在 2024 年正式替代了 FID(First Input Delay),衡量页面对用户交互的整体响应能力。

为什么用 INP 替代 FID?

定义与阈值

采集方式

分析方法

Chrome DevTools 的 Performance 面板录制用户操作后,Interactions 行会显示每次交互的耗时。点击具体的交互条目,可以看到事件处理函数的耗时分解:Input Delay(输入延迟)、Processing Time(处理时间)、Presentation Delay(渲染延迟)。

优化策略

FID 只测量第一次交互的输入延迟(从用户输入到回调开始执行),不算回调执行时间。INP 测量所有交互中从输入到下一帧视觉更新的完整耗时(包括事件处理、DOM 更新、浏览器绘制),取 P95。所以一个页面可能 FID 很好(第一次交互快),但 INP 很差(后续交互卡顿)。

Q6: 性能优化的系统性思维

这就是回答”你做过哪些性能优化”的完整框架。 性能优化不是堆砌技术,而是数据驱动的决策过程。

系统性优化框架

构建优化

网络优化

渲染优化(Core Web Vitals)

缓存策略

框架优化

用”发现什么问题 → 分析原因 → 选择方案 → 量化结果”的叙事框架。先用 Lighthouse 和 web-vitals 采集真实用户数据,按 Core Web Vitals 指标定位瓶颈,然后优先做 ROI 最高的优化:图片优化(投入小收益大)> 代码分割 > SSR。

CDN 加速原理

CDN(Content Delivery Network,内容分发网络)是网络优化中最核心的一环。理解 CDN 的工作原理,能帮你从”知道要用 CDN”提升到”知道怎么配置 CDN 才最有效”。
CDN 的三大核心机制: CDN 的配置策略:
回答 CDN 时不要只说”用 CDN 加速”,要说出三个层次:DNS 智能解析(怎么找到最近的节点)、缓存策略(强缓存 vs 协商缓存怎么选)、版本控制(hash 命名 vs 查询参数的区别)。想听到的是你理解”CDN 不是配了就完事,缓存策略配错了反而会导致用户拿到旧资源”。

接口预热

接口预热(API Pre-warming)是指在用户实际请求之前,提前触发接口调用,让服务器缓存或 CDN 边缘缓存进入”热”状态,从而在真实用户请求时直接命中缓存、快速响应。
适用场景:
接口预热的核心思想是”把慢的第一次请求提前到用户来之前完成”——通过定时任务、Service Worker 或部署脚本,在用户到达前把缓存填满,让每一次真实请求都命中缓存。

HTTP/2 和 HTTP/3 的优化原理

HTTP 协议的演进直接影响网络优化的上限。HTTP/1.1 的队头阻塞问题催生了 HTTP/2 的多路复用,而 TCP 层的队头阻塞又催生了 HTTP/3 基于 QUIC 的全新传输层。
HTTP/2 多路复用原理: HTTP/1.1 的核心问题是应用层队头阻塞——同一个 TCP 连接上,请求必须排队,第一个请求没完成,后面的请求全部阻塞。浏览器被迫开 6 个 TCP 连接来并行加载资源。 HTTP/2 引入了流(Stream)的概念:一个 TCP 连接被分成多个独立的流,每个流可以独立传输请求和响应。资源被拆成帧(Frame),不同流的帧可以在同一个连接上交错发送,到达后重组。 HTTP/3 QUIC 原理: HTTP/2 虽然解决了应用层的队头阻塞,但底层 TCP 仍然存在队头阻塞——TCP 保证字节流的有序交付,一个包丢失会阻塞整个连接上的所有流。 HTTP/3 用 QUIC(Quick UDP Internet Connections)替代了 TCP。QUIC 基于 UDP,在传输层实现了独立的流控制:每个流的包丢失只影响该流,不影响其他流。同时 QUIC 将 TLS 1.3 内置到握手中,首次连接只需 1-RTT,后续连接支持 0-RTT。
回答 HTTP 协议演进时,抓住一条主线:每一版都在解决”队头阻塞”。HTTP/1.1 有应用层队头阻塞(请求排队),HTTP/2 解决了应用层但还有 TCP 层队头阻塞(丢包阻塞整个连接),HTTP/3 用 QUIC 在传输层彻底消除了队头阻塞。同时指出:HTTP/2 的 Server Push 已被废弃,现代最佳实践是用 103 Early Hints 预加载资源。

Q7: 高频面试题

及格回答 vs 满分回答

问题:“说说你做过的性能优化” 及格回答(60-70分):
“图片压缩、代码分割、懒加载、CDN、缓存。”
问题出在:列举技术点但缺乏数据和框架。 满分回答(90-100分):
我用数据驱动的方式做性能优化。首先用 Lighthouse 和 web-vitals 采集真实用户数据,发现 LCP 是 4.2s(阈值 2.5s),主要瓶颈是首屏图片和关键 CSS。然后按 ROI 排序制定优化方案。
图片优化:从 PNG 转 WebP(体积减少 60%),加 <link rel=preload> 提前发现资源,用 <picture> 提供 AVIF/WebP/JPEG 三级降级。非首屏图片用 loading="lazy" 延迟加载。LCP 从 4.2s 降到 2.1s。关键 CSS 内联到 <head>(控制在 14KB 以内,匹配 TCP 初始拥塞窗口),非关键 CSS 异步加载。FCP 从 2.8s 降到 1.5s。[2]
JS 优化:路由级代码分割 + React.lazy,首屏 JS 从 800KB 降到 200KB。Addy Osmani 在《The Cost of JavaScript》中指出,长任务是 INP 的首要元凶——我在长任务中使用 scheduler.yield() 主动让出主线程,INP 从 350ms 降到 120ms。最终 LCP 2.1s(良好)、FCP 1.5s(良好)、CLS 0.05(良好)、INP 120ms(良好),转化率也有提升。
CLS 优化方面,排查发现两个主要来源:首屏大图没有设置宽高比(加了 width/height 属性),Web Font 加载导致文字重排(改用 font-display: optional)。CLS 从 0.25 降到 0.05。

常见追问

题目来源于真实面经(字节/腾讯/阿里前端面试、牛客网、haizlin/fe-interview),按难度递进。
答题要点: 必须说出 INP 已经替代了 FID,以及两者测量方式的本质差异。高分回答: “Core Web Vitals 有三个:LCP(Largest Contentful Paint,最大内容绘制,衡量加载速度,目标 ≤ 2.5s)、INP(Interaction to Next Paint,交互到下一帧,衡量交互响应,目标 ≤ 200ms)、CLS(Cumulative Layout Shift,累积布局偏移,衡量视觉稳定性,目标 ≤ 0.1)。INP 在 2024 年正式替代了 FID(First Input Delay)。核心区别:FID 只测量第一次交互的输入延迟(从用户输入到回调开始执行),不算回调执行时间。INP 测量所有交互中从输入到下一帧视觉更新的完整耗时(包括事件处理、DOM 更新、浏览器绘制),取 P95。所以 INP 更准确地反映了页面的整体交互响应能力——一个页面可能 FID 很好(第一次交互快),但 INP 很差(后续交互卡顿)。”
答题要点: 区分”首次内容”和”最大内容”,说明两者优化策略的差异。高分回答: “FCP 衡量浏览器渲染出第一个 DOM 内容的时间(文本、图片、SVG),是用户感知’页面开始有东西了’的起点。LCP 衡量页面最大内容元素渲染完成的时间,是用户感知’主要内容加载完了’的标志。优化 FCP 的重点是减少阻塞:内联关键 CSS(控制在 14KB 以内)、非关键 CSS 异步加载、字体用 font-display: swap。优化 LCP 的重点是资源发现时机:<link rel=preload> 提前发现首屏资源、fetchpriority='high' 优先加载、WebP/AVIF 减少体积、SSR 直出 HTML。最常见的坑:loading='lazy' 误加在首屏图片上——lazy load 会延迟图片加载,严重影响 LCP。”
答题要点: 解释 Session Window 的时间窗口和最大值选取逻辑。高分回答: “CLS 使用 Session Window 算法计算。规则是:5 秒内连续发生的布局偏移算一个 session,session 内所有偏移值累加。如果两次偏移间隔超过 5 秒,就开启一个新的 session。最终 CLS 值取所有 session 中的最大值。关键细节:
  1. 用户交互(点击、滚动)引起的偏移不计入 CLS(hadRecentInput 属性)
  2. session 的最大持续时间是 5 秒——即使偏移一直持续,超过 5 秒也会被截断
  3. 这个算法避免了’页面加载完成后持续微小偏移’被无限累加的问题”
答题要点: 拆解 INP 的 Input Delay、Processing Time、Presentation Delay。高分回答: “INP 的完整耗时分为三个阶段:
  1. Input Delay(输入延迟):从用户交互到事件回调开始执行的等待时间。瓶颈通常是主线程被其他任务占用。优化:用 scheduler.yield() 在长任务中让出主线程。
  2. Processing Time(处理时间):事件回调的执行时间。优化:拆分长任务、减少 DOM 操作、用 Web Worker 处理计算密集型逻辑。
  3. Presentation Delay(渲染延迟):从 DOM 更新到浏览器绘制下一帧的等待时间。优化:减少 DOM 节点数(虚拟列表)、避免强制同步布局(读写分离)。
在 Chrome DevTools 的 Interactions 面板中,可以清楚看到每个交互的三段时间分解。”
答题要点: 对比两种 API 的使用场景和优缺点。高分回答: “两者都能获取性能数据,但使用方式不同:performance.getEntries() 是拉取模式——需要主动调用获取当前已有的性能条目。适合一次性查询,但可能遗漏后续产生的条目。PerformanceObserver 是推送模式——注册回调后,新的性能条目产生时自动通知。适合持续监控,不会遗漏数据。而且可以用 buffered: true 参数获取注册前已经产生的条目。
生产环境推荐用 PerformanceObserver,因为它不会遗漏数据,且不会阻塞主线程。”
答题要点: 给出具体的操作步骤和结果解读方法。高分回答: “Lighthouse 的使用步骤:
  1. 打开 Chrome DevTools → Lighthouse 面板
  2. 选择设备(Mobile/Desktop)和审计类别(Performance)
  3. 点击 Analyze page load
  4. 用无痕模式录制(排除扩展干扰)
结果解读重点:
  • Performance Score:0-100 分,90+ 为良好
  • Core Web Vitals:FCP、LCP、CLS、INP 的具体数值和达标情况
  • Opportunities:可优化的建议,按预期收益排序
  • Diagnostics:诊断信息,如未使用的 JS/CSS 体积
常见误区:Lighthouse 是实验室数据(Lab Data),不代表真实用户体验。要结合 Chrome UX Report(CrUX)的现场数据(Field Data)一起看。”
答题要点: 给出系统性的排查流程,从网络到渲染逐层定位。高分回答: “排查首屏白屏,按以下流程逐层定位:
  1. Network 面板看瀑布图:TTFB 是否过长(> 600ms)?是否有串行请求阻塞?是否有 301/302 重定向?
  2. Performance 面板看时间轴:JS 执行是否阻塞渲染(Long Task > 50ms)?CSS 是否阻塞 DOM 解析?LCP 元素是什么?什么时候渲染出来的?
  3. Lighthouse 审计:看 Opportunities 中的建议,按影响排序。
  4. 具体场景:
    • TTFB 长 → 服务端慢,加 CDN、优化接口
    • JS 阻塞 → 代码分割、defer/async
    • CSS 阻塞 → 内联关键 CSS、preload 非关键 CSS
    • 图片阻塞 → preload、WebP/AVIF、fetchpriority
关键技巧:用无痕模式(排除扩展干扰),清空缓存模拟首次访问。”
答题要点: 解释可视区域渲染的核心机制,以及适用场景。高分回答: “虚拟列表(Virtual List)的核心原理是:只渲染可视区域内的 DOM 节点,滚动时动态替换内容。实现步骤:
  1. 计算可视区域能容纳多少个列表项(containerHeight / itemHeight)
  2. 根据滚动偏移量计算当前应该显示哪些项(startIndex = scrollTop / itemHeight)
  3. 用一个占位元素撑开总高度(totalHeight = itemCount * itemHeight)
  4. 只渲染 startIndex 到 startIndex + visibleCount 的 DOM 节点
适用场景:万级数据列表、聊天记录、日志查看器。不适用:列表项高度不固定、需要搜索/筛选的场景。”
答题要点: 区分强缓存和协商缓存的适用场景,说明 hash 命名的作用。高分回答: “CDN 缓存策略的核心原则是:内容变了就换 URL,没变就用缓存。带 hash 的静态资源(如 main.a1b2c3d4.js):文件内容变化时 hash 变化,URL 随之改变。这类资源应该配置强缓存 Cache-Control: max-age=31536000, immutable——缓存 1 年都没问题,因为新版本会生成新的 URL。不带 hash 的资源(如 index.html):URL 不变但内容可能更新。这类资源应该配置 Cache-Control: no-cache(不是 no-store)——每次使用前向服务器验证是否最新。常见误区:
  1. 用查询参数做版本号(style.css?v=1.2)——部分 CDN 不缓存带查询参数的请求
  2. HTML 设置了强缓存——用户永远看不到新版本
  3. 不同资源的缓存时间没有差异化——频繁更新的资源和几乎不变的资源应该用不同的缓存策略”
答题要点: 区分应用层并行和传输层多路复用,指出 Keep-Alive 仍然有队头阻塞。高分回答: “HTTP/1.1 的 Keep-Alive 只是复用 TCP 连接——多个请求共享一个连接,但必须排队(FIFO),第一个响应没回来,后面的请求全部阻塞。这就是应用层队头阻塞。浏览器被迫开 6 个 TCP 连接来并行加载,但每个连接内仍然是串行的。HTTP/2 的多路复用是真正的并行——一个 TCP 连接被分成多个流(Stream),不同流的帧(Frame)可以交错发送。请求 A 的响应慢,不影响请求 B 的帧传输。还有一个关键区别:HTTP/1.1 的 Keep-Alive 连接有空闲超时(通常 60-120 秒),超时后连接关闭需要重新建立。HTTP/2 的连接更加持久,且支持服务端主动推送(Server Push,虽然已被废弃)。HTTP/3 进一步解决了 TCP 层的队头阻塞:基于 QUIC/UDP,每个流独立控制丢包重传,一个流的丢包不影响其他流。“

底层源码:Chrome 是如何测量性能指标的?

Chrome 的 LCP 观测实现在 LargestContentfulPaint 类中(详见 Blink 源码:largest_contentful_paint.cc):
LCP 的观测器在每一帧渲染后检查是否有新的最大内容元素出现。它维护一个候选列表,每次有更大的元素进入视口时更新。用户交互后停止观测——因为交互后的渲染属于”用户驱动”而非”加载”。 详见 Blink 源码:event_timing.cc:
INP 测量的是从用户输入到下一帧视觉更新的完整耗时,包括事件处理、DOM 更新和浏览器绘制。与 FID 只测量第一次交互的输入延迟不同,INP 测量所有交互,取 P95(去除 5% 的异常值),更准确地反映了页面的整体交互响应能力。

延伸阅读与引用参考

  • Web Vitals 官方文档:Google 对 Core Web Vitals 指标的权威定义和优化建议。INP 于 2024 年 3 月 12 日正式替代 FID,测量所有交互的完整耗时(P95),而非仅第一次交互的输入延迟。[1]
  • Lighthouse 性能审计指南:Chrome 官方对 Lighthouse 工具的使用方法和指标解读。[2]
  • Chrome DevTools Performance 面板文档:Chrome 官方对性能面板的使用方法和指标解读。[3]
  • web-vitals 库:Google 官方的 Core Web Vitals 采集库,生产环境推荐使用。[4]
  • INP migration guide:web.dev 对 FID 到 INP 迁移的官方指南,解释了 INP 的三个阶段(Input Delay、Processing Time、Presentation Delay)。
  • Long Animation Frames (LoAF) API:Chrome 新推出的诊断主线程瓶颈的 API,比 Long Tasks 更精确。
  • 李兵 极客时间《浏览器工作原理与实践》:将渲染流水线拆解为 Style → Layout → Paint → Composite 四阶段,给出每帧 16.6ms 的时间分配模型。
  • Inside look at modern web browser:Lin Clark 在 Chrome Developers 发表的四部曲系列(2018),用漫画解析 Chromium 多进程架构和渲染流水线。
  • The Cost of JavaScript:Addy Osmani 在 V8 官方博客发表的文章,讨论长任务对交互响应的影响,并介绍用 scheduler.yield() 等方式拆分长任务。