Web Performance Fundamentals, v2

通过掌握最新的性能指标和网络优化技术,加快你的网站和网络应用的速度。改善关键的核心网络生命体征指标,如最大内容绘制(LCP)、累积布局偏移(CLS)和下一次绘制交互(INP)。使用Lighthouse、WebPageTest和真实用户监控等工具来获取可视性并调试性能问题,让你的网站快如闪电!

0-introduction

  • 工作坊主题:Web 性能
    • 为什么你的网站需要快。
    • 需要多快。
    • 如何实现。
  • 工作坊内容分解
    • Web 性能的重要性:为什么需要关注它。
    • 测量性能:深入探讨各种指标、数据收集方法及其含义。
    • 测试与工具:介绍常用的 Web 性能工作工具。
    • 设定适当的目标:你的网站需要多快。
    • 提升 Web 性能:针对每个指标,讨论具体优化策略。
  • 附加内容
    • 如何阅读瀑布图(waterfall chart)。
    • 如何阅读火焰图(flame chart)。
    • 关于统计和数据的基础知识,有助于理解工具和数据。
  • 学习所需准备
    • 一个 GitHub 账户。
    • 一个较新版本的 Google Chrome(课程中使用 131 版本)。
    • Node.js 20 版本。
  • 示例应用
    • 一个电子商务商店。
    • 下载地址:bit.ly/fund-web-perf
    • 安装步骤:npm install
  • 讲师介绍:Todd Gardner
    • 自 2002 年以来一直是一名软件工程师。
    • 也是一名顾问和培训师。
    • 创立了两家 SaaS 公司:
      • TrackJS:JavaScript 错误监控。
      • Request Metrics:Web 性能监控。
    • 联系方式:bit.ly/sup-todd

1-project-setup

  • 什么是 Web 性能?
    • 是指网站加载、渲染和响应访问者交互的速度和效率。
    • 它不仅仅是初始加载速度,还包括其他可感知的方面。
    • 即使用户界面加载很快,但点击按钮时感觉迟钝,用户仍然会觉得网站很慢。
  • 导致网站感觉“慢”的因素
    • 用户等待页面加载完成的时间过长。
    • 加载过程中元素跳来跳去(布局变化)。
    • 用户点击后有延迟。
    • 图片或视频加载或播放缓慢。
    • 滚动和动画卡顿。
  • 示例应用:Developer Stickers Online
    • 一个用于本工作坊的示例电子商务网站。
    • 启动项目
      • npm install
      • npm start
      • 访问 localhost:3000
  • 示例应用中观察到的性能问题
    • 页面加载非常缓慢。
    • 图片加载缓慢,内容布局发生移动。
    • 一个促销横幅很晚才弹出,导致页面内容下移。
    • 开发者工具(DevTools)中的发现
      • 控制台(Console)报告了糟糕的性能分数。
      • 网络(Network)面板显示:完成时间为 17.8 秒,加载了 20MB 的资源。
      • 性能(Performance)面板显示:整个加载过程长达 24 秒,充满了各种警告和提示。
  • 课程目标
    • 学会使用 DevTools 的性能面板、网络面板等工具来优化网站性能,并理解性能瓶颈所在。

2-user-expectations-of-performance

  • 为什么 Web 性能很重要?
    • 有三个主要原因:
      1. 用户体验 (User Experience)
      2. 搜索引擎优化 (SEO)
      3. 在线广告 (Online Advertising)
  • 1. 用户体验(User Experience)
    • 核心是网站是否达到或超过了用户的期望。
    • 用户的期望基于他们以往的经验、正在执行的任务以及网站对他们的重要性。
    • 缓慢的网站会让用户感到愤怒和沮丧,最终导致他们离开。
  • 用户的性能期望(基于学术研究)
    • IEEE 研究
      • 为了有效的沟通,系统应在用户请求后的 2 秒 内给出响应。
      • 超过 2 秒的等待会打断用户的注意力,影响其生产力。
    • 可用性工程研究
      • 0.1 秒内的响应:用户感觉是 瞬时 的。
      • 1 秒内的响应:用户会注意到延迟,但不会打断他们的思绪流程。
      • 10 秒或以上的响应:几乎所有用户都会感到沮丧,注意力被完全打断。
  • 针对网站的特定用户期望
    • 3 秒加载时间:一项大型研究发现,40% 的用户会放弃一个加载时间超过 3 秒的网站。
    • 糟糕体验的后果75% 的用户在经历了一次他们认为“慢”的体验后,永远不会再回来。他们会失去对你网站的信任。
    • 性能目标需要根据你的应用类型和用户的具体期望来设定,这将在后面的章节中详细讨论。

3-performance-benefits-seo-advertising

  • 搜索引擎优化(SEO)
    • 指的是帮助搜索引擎理解你的内容并对其进行排名的能力。
    • 在搜索结果中排名第一至关重要,因为流量会随着排名的下降而急剧减少。
    • 数据表明:排名第一的结果获得的点击量远超其他位置。
  • 性能与 SEO 的关系
    • 自 2020 年起,Google 已将 页面体验指标(page experience metrics) 纳入其搜索排名算法。
    • 核心 Web 指标(Core Web Vitals) 是衡量性能的关键指标,它们是直接的排名因素
    • 这意味着,网站必须快才能获得好的排名。如果你的内容和其他网站一样好,但速度更慢,你就会输掉排名。
    • 数据显示:排名靠前的网站通常有更好的性能分数(如 LCP 和 CLS)。
  • 在线广告(Online Advertising)
    • 性能差会导致 跳出率(bounce rate) 增高,浪费广告预算。
    • 广告活动示例
      • 假设花费$1000 美元带来 1600 名用户。
      • 如果网站跳出率是 60%,那么 960 名用户会因为糟糕的体验立即离开,只有 640 名成为真正的潜在客户。每个潜在客户的成本是$1.56。
    • 性能改善的案例(Fernspace)
      • 网站性能提升 65%。
      • 结果:跳出率降低 20%,用户在页面上的停留时间增加了 200%。
    • 将此改善应用到示例中
      • 跳出率从 60%降至 48%。
      • 同样花费$1000,现在可以获得 832 名潜在客户(增加了 192 名)。
      • 每个潜在客户的成本降至$1.20。
      • 结论:提升 Web 性能可以显著提高在线广告的投资回报率。
  • 性能与收入(Revenue)的直接联系
    • 沃尔玛(Walmart):加载时间每提升 100 毫秒,网站的增量收入就增加 1%
    • Skill.com(招聘网站):加载时间与转化率直接相关。
      • 2.4 秒 加载时间 -> 1.9% 转化率。
      • 3.3 秒 加载时间 -> 1.5% 转化率。
      • 超过 5 秒 -> 转化率低于 0.6%
    • WPO Stats 网站:收集了大量关于性能如何影响核心业务指标的案例研究,可以用来证明对 Web 性能进行投入的必要性。

4-waterfall-charts

  • 测量 Web 性能的议程
    • 传统指标(Legacy Metrics):了解它们的定义以及为什么它们不再那么重要。
    • 核心 Web 指标(Core Web Vitals):Google 用于评估性能和影响 SEO 的指标。
    • 其他相关指标
    • 如何捕获这些指标
    • 浏览器支持情况
    • 附加内容:瀑布图(Waterfall Charts)和火焰图(Flame Charts)。
  • 瀑布图(Waterfall Charts)
    • 一种可视化工具,用于显示网页资源(HTML, CSS, JS, 图片等)如何随时间加载。
    • 单个资源请求条的构成
      • 排队和连接时间(Queuing and Connecting):请求开始前等待的时间,可能因为浏览器连接数限制等原因。
      • 等待时间(Waiting - TTFB):发送请求后,等待服务器返回第一个字节数据的时间。
      • 内容下载时间(Content Download):下载资源本身的耗时。
      • 主线程等待时间(Waiting on Main Thread):资源下载后,等待浏览器主线程有空处理它的时间。
    • 资源类型的颜色编码
      • 蓝色:HTML 文档。
      • 紫色:CSS 样式表。
      • 黄色:JavaScript 脚本。
      • 绿色:图片。
      • 青色:字体。
      • 灰色:其他资源(如 fetch 请求、iframes 等)。
    • 瀑布图示例流程
      1. 用户访问页面,浏览器开始下载 HTML 文档。
      2. 解析 HTML 时,发现其他资源(CSS, JS, 图片)。
      3. 浏览器开始下载这些资源,但受限于并发连接数。
      4. 资源根据优先级和连接可用性依次下载。
      5. 瀑布图清晰地展示了这种依赖关系和加载顺序,是性能优化的重要工具。

5-measuring-domcontentloaded-load-events

  • 传统性能指标(Legacy Metrics)
    • "Legacy" 指的是这些指标过去被用来衡量性能,但现在已不作为主要标准,了解它们仍然很重要。
  • 1. DOMContentLoaded (DCL)
    • 定义:当初始 HTML 文档被完全加载和解析完成之后触发,无需等待样式表、图像和子框架的完成加载。同步的 JavaScript 脚本也已执行完毕。
    • 意味着什么:页面的 DOM 结构已经形成,可以安全地通过 JavaScript 进行操作。
    • 不意味着什么:此时图片和其他资源可能仍在加载中。
    • 在瀑布图上的位置:通常在 HTML 文件和任何阻塞性 JavaScript 下载并执行完毕后触发。
    • 如何监听该事件
      window.addEventListener("DOMContentLoaded", (event) => {
        console.log("DOM fully loaded and parsed");
        console.log(event.timeStamp); // 事件发生时的时间戳
      });
      
  • 2. Load 事件
    • 定义:当整个页面以及所有依赖资源(如样式表、脚本、图片、iframe 等)都已完成加载时触发。
    • 重要细节:仅指当时已知的资源。通过 JavaScript 动态添加的资源如果在load事件之后加载,则不会影响此事件。懒加载(lazy-loading)的资源是例外。
    • 意味着什么:浏览器标签页上的加载指示器(小圈圈)会停止旋转,表示页面加载完成。
    • 在瀑布图上的位置:位于所有已知资源加载完成的最末端。
    • 如何监听该事件
      window.addEventListener("load", (event) => {
        console.log("Page fully loaded");
        console.log(event.timeStamp); // 事件发生时的时间戳
      });
      

6-the-problem-with-legacy-metrics

  • 传统指标的问题所在
    • 过去,DOMContentLoaded (DCL) 和 Load 是衡量性能的唯一方法。
    • 在传统的网站(服务器渲染为主)中,这些指标是有意义的。例如,jQuery 的 $(document).ready() 就对应 DCL,它确保在操作 DOM 之前,DOM 已经准备就绪。
  • 2010 年代的变革:客户端渲染(Client-Side Rendering, CSR)
    • 随着 Backbone, Knockout, React, Vue 等框架的兴起,Web 应用的构建方式发生了巨大变化。
    • CSR 的典型模式
      1. 服务器只返回一个非常简单的 HTML 文件,通常只包含一个空的 <div> 容器(如 <div id="app"></div>)。
      2. 因为这个 HTML 文件很小且没有依赖资源,DOMContentLoadedLoad 事件会 几乎立即 触发。
      3. 之后,一个庞大的 JavaScript 包才开始下载、解析和执行,在客户端动态构建整个用户界面。
  • CSR 带来的后果
    • 对于 CSR 应用,DCL 和Load指标变得 毫无意义
    • 它们报告的加载时间可能非常快,但用户实际上可能正盯着一个白屏长达数秒,等待 JavaScript 渲染页面。
    • 这些传统指标 不再能反映真实的用户感知性能
  • Google 为何需要新指标
    • Google 的核心业务是提供最佳的搜索结果,而快速的网站能提供更好的用户体验。
    • 为了客观地比较网站性能并将其作为排名依据,Google 需要新的指标,这些指标必须:
      1. 反映用户的真实感知
      2. 与网站构建技术无关(无论是服务器渲染还是客户端渲染)。
  • 解决方案:核心 Web 指标(Core Web Vitals)
    • Google 推出了一套全新的测量标准,旨在解决上述问题,客观衡量用户体验。

7-largest-contentful-paint-lcp

  • 核心 Web 指标(Core Web Vitals)概述
    • 衡量用户体验的三个方面:
      1. 加载性能 (Loading Performance):网站可见内容加载速度。由 最大内容绘制 (LCP) 衡量。
      2. 加载稳定性 (Loading Stability):加载过程的平滑度。由 累积布局偏移 (CLS) 衡量。
      3. 交互性 (Interactivity):用户与页面交互的响应速度。由 下次绘制交互 (INP) 衡量。
    • 这些指标是直接影响 SEO 排名的因素。
  • 最大内容绘制 (Largest Contentful Paint, LCP)
    • 定义:测量视口中 最大 的可见图像或文本块完成渲染所需的时间。
    • 目的:作为页面主要内容可能已加载完成的代理指标。使用“最大”而不是“最重要”是因为它客观且难以被操纵。
    • 哪些元素被视为 LCP 候选元素?
      • <img> 元素。
      • poster 图像的 <video> 元素。
      • 具有 CSS background-image 的元素。
      • 包含文本的块级元素。
    • 排除规则(防止作弊)
      1. 元素的 opacity 不能为 0(不能是透明的)。
      2. 元素的大小必须小于整个页面(以排除全屏背景)。
      3. 低熵图像(low entropy images) 会被排除。
        • 是衡量图像数据密度的指标(每像素的比特数)。
        • 高质量的“英雄图片”具有高熵。而一个模糊、低质量的占位符图像熵很低。
        • 使用低熵占位符 不会 触发 LCP;LCP 会等待最终的高质量图像加载完成。
    • LCP 的测量何时停止?
      • 在用户首次进行交互(如点击、按键)后停止。滚动页面不会停止测量。
    • LCP 的“良好”标准小于等于 2.5 秒
  • 问答环节
    • 用户提早点击会怎样? 如果用户在 LCP 元素加载完成前就点击页面,LCP 的测量会提前停止,这可能会导致该次访问的 LCP 数据不准确。
    • DCL/Load 事件在单页应用(SPA)中还有用吗? 这些事件依然存在,但在 SPA 中通常会非常早地触发,因此对于衡量用户感知性能来说,它们的价值远不如 LCP。

8-cumulative-layout-shift-cls

  • 累积布局偏移 (Cumulative Layout Shift, CLS)
    • 定义:测量页面在加载过程中的 视觉稳定性。它量化了在没有用户交互的情况下,页面内容发生意外移动的总量。
    • 目的:衡量因元素跳动给用户带来的挫败感,这种跳动可能导致用户误点击。Google 通过该指标惩罚存在这种不良体验的网站。
    • 常见例子:广告或横幅异步加载,将现有内容向下推。
    • 互动演示shifty.site 这个小游戏极端地展示了 CLS 问题。
  • CLS 如何计算?
    • 每一次意外的布局偏移都会计算一个分数:
      • 布局偏移分数 = 影响分数 (Impact Fraction) * 距离分数 (Distance Fraction)
    • 影响分数:视口中受此次偏移影响的区域百分比(即移动的元素和导致移动的元素所占区域的总和)。
    • 距离分数:不稳定的元素移动的最大距离,除以视口的最大尺寸(宽度或高度)。
    • 移动端 vs. 桌面端:同样的布局偏移在移动设备上可能会得到 更差 的分数,因为视口更小,导致影响分数和距离分数的比例更大。
  • "累积"(Cumulative)的含义
    • 最终的 CLS 分数是页面生命周期内 所有 单独布局偏移分数的 总和
  • 排除规则
    • 在用户交互(如点击)后的 500 毫秒 内发生的布局偏移 不被计算 在内。这允许了用户触发的 UI 变化,例如展开一个折叠面板。
  • CLS 的“良好”标准小于等于 0.1
    • 在示例中,计算出的分数0.215远超标准,属于“差”,会受到 SEO 惩罚。

9-cumulative-layout-shift-q-a

  • 问:为什么网站头图(header)没有被包含在影响分数(impact fraction)中?
    • :因为在布局偏移事件中,头图本身的位置没有改变。影响分数只计算那些位置发生变化的元素。在示例中,头图是静态的,促销横幅出现在其下方,将下面的所有内容都向下推了。
  • 问:如果用户向下滚动页面,CLS 还会被计算吗?
    • :会。CLS 是在用户当前的视口(viewport)内测量的。如果用户滚动到一个新的区域,而该区域的某个元素加载缓慢并导致内容偏移,这个偏移同样会被计入 CLS 分数。这说明 CLS 不是一个固定的值,它对每个用户会话都是独特的,取决于他们的设备、网络和交互行为。Google 会聚合大量用户的真实数据(通常取第 75 百分位)来得出一个代表性的分数。
  • 问:服务器端渲染(SSR)能解决大部分布局偏移问题吗?
    • :不一定。虽然客户端渲染(CSR)是 CLS 的常见原因,但 SSR 网站同样可能存在 CLS 问题。一个主要原因是 未设定尺寸的资源,比如没有明确设置 widthheight 属性的图片。浏览器不知道为它预留多大空间,当图片最终加载完成时,就会推开周围的内容,造成布局偏移。
  • 问:布局偏移是基于 DOM 元素的吗?比如 <canvas> 内部的变化会计算吗?
    • :是的,CLS 基于 DOM 元素。在 <canvas> 元素 内部 发生的内容变化不会被 CLS 跟踪。但是,如果 <canvas> 元素本身加载时没有预留空间,导致它周围的 DOM 元素发生位移,那么这个位移是 被计算为 CLS 的。
  • 问:骨架屏(skeleton loaders)会产生布局偏移吗?
    • :不会,恰恰相反,骨架屏是 防止 布局偏移的核心技术之一。骨架屏的作用是为即将加载的内容预先占据空间。当真实内容替换掉骨架屏时,整体布局不会改变,因此不会产生布局偏移。
  • 问:<iframe> 引起的布局偏移会被计算吗?
    • :会。如果 <iframe> 元素本身加载时没有指定尺寸,导致其在父页面中引起内容移动,这会计入父页面的 CLS。但是,<iframe> 内部 内容发生的布局偏移不会“冒泡”到父页面,不会影响父页面的 CLS 分数。

10-flame-charts

  • 介绍火焰图(Flame Chart)
    • 为了理解最后一个核心 Web 指标——“下次绘制交互”(INP),我们需要了解火焰图。
    • 火焰图是 Chrome 等工具中用来可视化浏览器在毫秒级时间范围内执行任务的图表。
  • 火焰图的结构
    • 时间轴:横轴表示时间,通常单位是毫秒或微秒。
    • 调用栈:纵轴表示调用栈。上方的条块(父任务)调用下方的条块(子任务)。
    • 条块宽度:表示任务执行所花费的时间。
    • 示例task1 调用 task2task2 调用 task3。在火焰图中会显示为三层堆叠的条块。
  • 颜色编码
    • 灰色:顶层浏览器任务。
    • 蓝色:解析 HTML。
    • 粉色:布局(Layout)和绘制(Paint)事件。
    • 深黄色:顶层 JavaScript 任务,如评估和编译脚本。
    • 浅黄色:实际的 JavaScript 代码执行时间。
    • 绿色:浏览器扩展程序(通常可以忽略)。
  • 火焰图的重要性
    • 浏览器主线程(Main Thread) 是单线程的。
    • 它负责处理所有工作,包括:
      • 用户事件(点击、输入等)。
      • DOM 布局。
      • 页面绘制。
      • 执行 JavaScript。
    • 如果你的 JavaScript 代码执行时间过长(Long Task),它会 阻塞主线程,导致浏览器无法响应用户交互、无法更新页面,从而使用户感觉页面卡顿或无响应。

11-interaction-to-next-paint-inp

  • 下次绘制交互 (Interaction to Next Paint, INP)
    • 第三个核心 Web 指标,也是最常被误解的一个。
    • 定义:测量从用户进行交互(如点击、按键)到浏览器 下一次绘制 屏幕之间的时间。
    • 目的:衡量网站的 响应速度,即用户感觉交互是否流畅。
  • INP 的特点
    • 用户特定:这个分数是针对每一个真实用户的每一次会话产生的。如果用户没有进行交互,就不会有 INP 分数。因此,自动化测试工具通常无法报告 INP。
    • 交互类型:包括点击(click)、拖拽(drag)、触摸(touch)、按键(key press),但 不包括 滚动(scroll)。
  • INP 测量分解
    • 输入延迟 (Input Delay):从用户交互发生到事件处理程序开始执行的延迟。如果主线程正忙,这个延迟会很长。
    • 处理时间 (Processing Time):执行交互事件回调函数(如click事件监听器中的 JavaScript 代码)所需的时间。
    • 呈现延迟 (Presentation Delay):事件处理完成后,浏览器计算布局、样式并绘制下一帧所需的时间。
    • INP 是这三部分时间的总和。
  • 关键点
    • 取最差值:INP 记录的是用户在整个页面访问期间 最长 的一次交互延迟,而不是平均值。
    • 后知后觉:只有当用户离开页面时,最终的 INP 分数才能确定。
    • 设备依赖性强:在开发人员的高性能电脑上,INP 可能很好,但在用户的低端安卓设备上,相同的操作可能会慢得多。
    • 异步是关键:可以通过将耗时长的 JS 任务设为异步(如使用Promiseasync/awaitsetTimeout)来快速响应用户,从而优化 INP。例如,点击按钮后,可以立即显示一个加载动画(spinner),然后再异步处理耗时任务。这样“下次绘制”就是显示加载动画,INP 会非常低。
  • Google 的评分标准
    • 良好: ≤ 200 毫秒
    • 需要改进: > 200 毫秒 且 ≤ 500 毫秒
    • : > 500 毫秒

12-first-input-delay-fid

  • 首次输入延迟 (First Input Delay, FID) - 已弃用
    • FID 曾是第三个核心 Web 指标(2020 年至 2024 年)。
    • 在 2024 年,它被 INP 正式取代,因为 INP 能更好地衡量整体的用户交互性。
  • FID 测量的是什么?
    • FID 测量的是从用户 首次 与页面交互(例如点击按钮)到浏览器能够实际开始处理该事件的 延迟时间
    • 它主要关注的是 输入延迟(Input Delay) 部分,即主线程被阻塞的时间。
  • 为什么关注首次输入?
    • 因为用户首次交互时,页面通常仍处于繁忙的加载阶段(执行大量 JavaScript、下载资源等),主线程最有可能被阻塞。
  • 为什么 FID 被取代?
    • 只测量首次输入:FID 不关心首次交互之后的其他交互,但后续的交互卡顿同样会影响用户体验。
    • 测量不完整:它只测量了“延迟”,而没有测量事件处理本身所需的时间和后续的渲染时间。
    • INP 更全面:INP 测量了每一次交互的完整周期(延迟+处理+渲染),并取最差值,更能反映页面的整体响应能力。

13-time-to-first-byte-ttfb

  • 首字节时间 (Time to First Byte, TTFB)
    • 定义:一个衡量服务器响应速度的指标。它指从浏览器发起请求到接收到服务器响应的 第一个字节 所花费的时间。
    • 衡量对象:这几乎完全是一个衡量 服务器和网络性能 的指标,与前端代码(HTML, JS, CSS)关系不大。
    • 在瀑布图中的位置:通常是 HTML 文档请求中的“等待(Waiting)”部分。
  • TTFB 包含哪些环节?
    • 重定向时间 (Redirect Time):处理 HTTP 重定向的耗时。
    • DNS 查询 (DNS Lookup):将域名解析为 IP 地址的耗时。
    • TCP 连接 (TCP Connection):与服务器建立 TCP 连接的耗时。
    • TLS 协商 (TLS Negotiation):建立 HTTPS 安全连接的耗时。
    • 服务器处理时间 (Server Processing Time):服务器接收请求后,执行后端逻辑(如数据库查询)并准备响应的耗时。
  • TTFB 的重要性
    • 不是核心 Web 指标:TTFB 本身不会直接影响 SEO 排名。
    • 是性能的基础:TTFB 是所有后续前端性能指标的 基础。如果 TTFB 很慢,那么 FCP 和 LCP 等指标也绝不可能快。
    • Google 的建议标准小于 800 毫秒。对于需要进行数据库查询才能生成的动态页面来说,这是一个具有挑战性的目标。

14-first-contentful-paint-fcp

  • 首次内容绘制 (First Contentful Paint, FCP)
    • 定义:测量从页面开始加载到 任何内容(如文本、图像、canvas元素)首次被渲染到屏幕上的时间。
    • 目的:标记用户首次看到页面正在加载的信号,从白屏变为有内容,给用户一个积极的反馈。
  • FCP 在加载过程中的位置
    • 通常的加载顺序是:
      1. 用户导航,页面白屏。
      2. TTFB(服务器返回第一个字节)。
      3. 浏览器解析文档,下载资源。
      4. FCP(屏幕上出现第一个元素)。
      5. LCP(页面中最大的元素绘制完成)。
      6. Load(所有资源加载完毕)。
    • 注意:这个顺序会因网站架构(如客户端渲染)而异。
  • FCP 的重要性
    • 不是核心 Web 指标:它不直接影响 SEO 排名。
    • 与 LCP 直接相关:FCP 是 LCP 的前提,是衡量页面加载进度的重要里程碑。
    • Google 的建议标准小于 1.8 秒。这意味着从 TTFB 到 FCP,浏览器大约有 1 秒的时间来解析和渲染出第一个元素。
  • 指标间的关系
    • 通常情况下:LCP >= FCP >= TTFB
    • 在非常简单的页面上,FCP 和 LCP 可能几乎同时发生。

15-performance-api

  • 如何通过代码捕获性能指标
    • 浏览器提供了两个主要的 API 来访问性能数据:
      1. Performance API
      2. PerformanceObserver API
  • Performance API (window.performance)
    • performance.now()
      • 提供了一个高精度的时间戳(精确到微秒)。
      • 它是相对于页面开始导航的时间(timeOrigin)计算的。
      • Date.now() 精确得多,适用于测量微小的性能差异。
    • performance.timeOrigin
      • 一个高精度的时间戳,表示页面导航开始的时刻。
      • performance.timeOrigin + performance.now() 可以得到一个与 Date.now() 类似但精度更高的当前绝对时间。
    • performance.getEntries()
      • 返回一个包含各种性能条目的数组。
      • 这些条目包含了页面加载过程中所有资源的详细计时信息,相当于以编程方式访问 Chrome 开发者工具“网络”面板中的数据。
      • 你可以获取到每个资源的 DNS 查询、TCP 连接、下载等各个阶段的耗时。

16-performance-observer

  • Performance API 的问题:观察者效应 (Observer Effect)
    • 如果你频繁地、同步地调用 performance.getEntries() 来获取数据,这种测量行为本身就可能占用主线程资源,从而影响页面性能,导致测量结果不准确。
  • PerformanceObserver API
    • 这是解决“观察者效应”的推荐方案。
    • 工作方式:它允许你 订阅 某种类型的性能事件。当事件发生后,浏览器会在空闲时异步地将数据推送给你的回调函数,而不会阻塞主线程。
    • 示例:监听布局偏移(CLS)
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          console.log("Layout Shift:", entry.value);
        }
      });
      observer.observe({ type: "layout-shift", buffered: true });
      
    • buffered: true 的重要性: 这个选项让你能够获取在观察者被创建 之前 就已经发生的性能事件。这对于那些在页面加载后期才执行的监控脚本至关重要,可以确保不会丢失早期的性能数据。
  • web-vitals.js
    • 对于只想获取核心 Web 指标(LCP, CLS, INP)的开发者来说,最简单的方法是使用 Google 官方提供的 web-vitals JavaScript 库。
    • 它封装了 PerformanceObserver 的复杂性,提供了简单的回调函数,如 onLCP, onCLS, onINP
    • 绝大多数情况下,推荐使用这个库而不是直接操作底层的 API。

17-browser-support-for-performance-metrics

  • 性能指标由浏览器引擎实现
    • Blink: Chrome, Edge, Opera 等(市场份额最大)。
    • WebKit: Safari(包括 iOS 上的所有浏览器)。
    • Gecko: Firefox。
  • 核心 Web 指标的浏览器支持情况
    • 传统指标 (DOMContentLoaded, Load): 所有浏览器均支持。
    • TTFB, FCP: 所有主流浏览器均支持。
    • 核心 Web 指标 (LCP, CLS, INP):
      • Blink (Chrome 系): 完全支持
      • Gecko (Firefox): 支持 LCP, 不支持 CLS 和 INP。
      • WebKit (Safari): 完全不支持 LCP, CLS, INP。
  • 这意味着什么?——一个巨大的盲区
    • 无法 通过标准的 Web Vitals API 收集到来自 Safari 用户 的核心 Web 指标数据。
    • 考虑到 Safari 在桌面端,尤其是在移动端(iPhone/iPad)占有重要市场份额,这部分用户的数据是缺失的。
    • 你的 Core Web Vitals 报告只反映了 Chrome 和部分 Firefox 用户的情况,并不能代表所有用户。
  • 应对策略
    • 尽管有盲区,但优化 Core Web Vitals 仍然至关重要,因为它们影响着最大用户群(Chrome 用户)的体验和 Google 的 SEO 排名。
    • 手动测试:定期在真实的(尤其是旧款)iPhone 设备上手动测试你的网站,以确保 Safari 用户的体验不会太差。这是弥补数据缺失的最直接方法。

18-testing-performance

  • 两种主要的性能测试数据类型
  • 1. 实验室数据 (Lab Data)
    • 定义:在受控的、可预测的环境中收集的性能数据。
    • 示例
      • 在你自己的电脑上运行 Chrome Lighthouse 测试。
      • 使用 WebPageTest 等综合性测试工具。
    • 优点
      • 结果稳定,可重复。
      • 便于在开发过程中定位和调试问题。
    • 缺点
      • 不能代表真实的用户体验。因为它通常在高速网络和高性能设备上运行。
  • 2. 现场数据 (Field Data) / 真实用户监控 (Real User Monitoring, RUM)
    • 定义:从访问你网站的真实用户的浏览器中收集的性能数据。
    • 工作方式:通过在网站中嵌入脚本,利用 Performance API 捕获真实用户的性能指标,并发送回分析服务器。
    • 优点
      • 是性能的最终真相。它反映了在各种设备、网络和地理位置下的真实用户体验。
    • 缺点
      • 数据量大且“嘈杂”,需要统计学方法来解读。
      • 部署和分析更复杂。
  • 两者关系:诊断 vs. 体验
    • 实验室数据是“诊断工具”:当你发现性能问题时,用它来重现问题、测试解决方案。Lighthouse 得分 100 并不意味着你的网站就快。
    • 现场数据是“真实体验”:用它来判断你的网站对于真实用户来说是否足够快。Google Core Web Vitals 报告中的数据就属于现场数据。
    • 一个常见的误区:得到完美的实验室分数,但现场数据显示许多用户体验不佳。反之亦然。两者不可相互替代。

19-statistics

  • 为什么现场数据需要统计学?
    • 因为现场数据包含了成千上万个来自不同用户的样本,你需要一种方法来理解这整个数据集的特征。
  • 为什么“平均值 (Average)”不可靠?
    • 容易被误导:一个 80 分的平均分,可能意味着所有人的体验都是 80 分,也可能意味着一半人是完美的 99 分,另一半人是糟糕的 60 分。平均值隐藏了分布情况。
    • 易受极端值影响:一两个极差的数据点(如网络异常导致加载 3 分钟)就能极大地拉低整个平均分,使其失去代表性。
  • 更好的方法:百分位数 (Percentiles)
    • 定义:一个值,表示数据集中有多大比例的观测值低于它。
    • 常用的百分位数
      • 50 百分位 (p50)中位数 (Median):数据集的中间值。50%的用户体验比这个值好,50%比这个值差。
      • 75 百分位 (p75):代表了 大多数用户 的体验。75%的用户体验好于或等于这个值。这是 Google 在核心 Web 指标报告中使用的标准
      • 95/99 百分位 (p95/p99):代表了 最差的用户体验,同时能过滤掉一些极端异常的数据。
  • 如何正确使用两种数据
    1. 现场数据 (p75) 来了解你的网站对真实用户的性能表现。这是你的“黄金标准”。
    2. 实验室数据 来诊断性能瓶颈并验证优化效果。
    • 让实验室数据更接近现实:在进行实验室测试时,应该模拟真实用户的环境,如:
      • 模拟移动设备视图。
      • 节流网络速度(如慢速 4G)。
      • 降低 CPU 性能。

20-google-lighthouse

  • Web 性能测试工具:Google Chrome DevTools
    • 免责声明:DevTools 正在积极开发中,界面和功能可能会随版本变化而改变。
  • 使用 DevTools 进行性能测试的最佳实践
    • 将 DevTools 分离成独立窗口
      • 点击 DevTools 右上角的三个点,选择第一个图标(Undock into separate window)。
      • 原因:避免 DevTools 窗口本身占用视口(viewport)空间,从而影响页面布局和性能测试的准确性。
  • Lighthouse:入门级性能分析工具
    • Lighthouse 是 DevTools 中的一个面板,用于生成一个全面的性能报告,并给出一个 0-100 的性能分数。
    • 核心任务:模拟真实用户环境,而不是在你强大的开发机上运行测试。
  • 配置 Lighthouse 以模拟真实用户
    1. 切换到移动设备视图
      • 打开 设备工具栏 (Device Toolbar)(左上角的手机/平板图标)。
      • 选择一个有代表性的设备,例如 iPhone 12 Pro,模拟常见的屏幕尺寸。
      • 你也可以自定义设备尺寸,例如创建一个“小型笔记本电脑”的配置(如 1366x768 分辨率,非 Retina 屏幕)。
    2. 模拟网络状况(网络节流)
      • 切换到 Network (网络) 面板。
      • Throttling (节流) 下拉菜单中,选择一个预设(如 Fast 3G)或添加一个自定义的网络配置。
      • 示例自定义配置:“美国咖啡店 WiFi”,配置为 10Mbps 下载/4Mbps 上传/50ms 延迟。
    3. 模拟 CPU 性能
      • 切换到 Performance (性能) 面板。
      • 点击右上角的齿轮图标(设置)。
      • CPU Throttling (CPU 节流) 中,选择一个 slowdown 选项,例如 6x slowdown,以模拟性能较差的移动设备。
    • 注意:启用节流后,DevTools 会显示黄色警告图标,提醒你当前处于模拟环境中。
  • 运行 Lighthouse 报告
    • 返回 Lighthouse 面板,确保 Throttling (节流) 选项设置为 DevTools Throttling (Simulated)
    • 选择 Performance (性能) 类别。
    • 点击 Analyze page load (分析页面加载)
    • 结果分析
      • 报告会显示核心 Web 指标(FCP, LCP, CLS)和其他实验室指标。
      • 注意:Lighthouse 无法测量 INP,因为它不执行用户交互。
      • 报告会提供一个页面加载的 胶片视图 (filmstrip)
      • 提供一系列 诊断建议
      • 最重要的功能是:点击 View Original Trace (查看原始跟踪记录),这会将你带到 Performance (性能) 面板,进行更深入的分析。

21-analyzing-results-in-performance-tab

  • Performance (性能) 面板:深入分析的工具
    • 这是 Lighthouse 报告背后的原始数据,包含了页面加载过程中所有事件的详细信息,初看起来可能非常复杂。
    • 主要由 瀑布图 (Waterfall Chart)火焰图 (Flame Chart) 组成。
  • Performance 面板的构成与使用
    1. 时间轴 (Timeline)
      • 位于最顶部,显示整个加载过程的总时长。
      • 可以通过拖动选择器来 放大 (zoom in) 特定的时间范围,这是分析的关键。
    2. 胶片视图 (Filmstrip)
      • 在时间轴下方,显示页面在不同时间点的视觉快照。
      • 将鼠标悬停在上面,可以直观地看到页面从白屏到内容出现的过程。
    3. 瀑布图 (Waterfall Chart) 区域
      • 显示网络请求的详细情况。
      • 可以看到资源请求的排队时间、下载时间等。
      • 红色角标表示该资源是 渲染阻塞 (render blocking) 的。
    4. 火焰图 (Flame Chart) 区域
      • 显示主线程的 CPU 活动。
      • 可以看到 HTML 解析、CSS 计算、JavaScript 执行等任务的耗时和调用关系。
      • 注意:网络请求(瀑布图)和 CPU 任务(火焰图)的时间尺度通常不同。分析 CPU 任务需要放大到毫秒甚至微秒级别。
  • 如何导航和分析
    • 缩放:使用鼠标滚轮或拖动时间轴选择器进行缩放。
    • 平移和导航:使用键盘上的 W, A, S, D 键可以像玩游戏一样平移和缩放视图,非常高效。
    • 关联分析:通过在瀑布图中找到一个资源加载完成的时间点,然后在火焰图中观察紧随其后的 CPU 任务,可以理解资源加载如何触发后续的解析和执行。
  • 问答:关于 Memory(内存)面板
    • 性能 API(PerformanceObserver无法 很好地报告真实用户的内存使用情况。
    • 有一个实验性的 performance.measureUserAgentSpecificMemory() API(仅 Chrome 支持),但为了保护用户隐私,返回的数据是经过模糊处理的,精度不高,难以用于精确定位内存泄漏。
    • 通常,内存问题需要使用 Memory (内存) 面板在本地进行诊断。
    • 对于 Web 性能问题,绝大多数瓶颈在于资源加载顺序、布局、图片优化等,而不是底层的 JavaScript 内存管理。

22-web-vitals-extension

  • Web Vitals Chrome 扩展程序
    • 由 Google 官方团队开发,是一个非常有用的、用于实时诊断核心 Web 指标的工具。
  • 如何安装和设置
    1. 从 Chrome 网上应用店搜索 "Web Vitals"并安装。
    2. 将扩展程序固定到工具栏以便快速访问。
    3. 点击扩展图标,进入设置(齿轮图标),启用 "Console logging" (控制台日志)
  • 扩展程序的功能
    1. 实时显示指标
      • 当你在一个页面上时,扩展程序的图标会实时显示该页面的核心 Web 指标分数(LCP, CLS, INP)。
      • 指标会根据颜色(绿/黄/红)来表示好坏。
    2. 详细的控制台输出
      • 启用控制台日志后,当页面加载完成或发生交互时,它会在 DevTools 的控制台中打印出每个核心 Web 指标的详细信息。
      • 对于 LCP/CLS:你可以展开日志,查看是哪个 DOM 元素导致了该指标。
      • 对于 INP:在你与页面进行交互(例如点击)后,它会记录并显示该次交互的 INP 值。
      • 这极大地简化了调试过程,无需手动编写PerformanceObserver代码。
    3. 显示现场数据 (Field Data)
      • 在访问 公开的、有足够流量的网站 时(例如developer.mozilla.org),该扩展不仅会显示你当前的 实验室数据 (Lab Data),还会显示来自 Chrome 用户体验报告 (CrUX)现场数据 (Field Data)
      • 它会告诉你,在过去 28 天里,真实用户中有多大比例的人在该网站上获得了“良好”、“需要改进”或“差”的体验。
  • 这个现场数据从何而来?
    • 这引出了下一个主题:Chrome 用户体验报告(CrUX)。

23-chrome-user-experience-report

  • Chrome 用户体验报告 (Chrome User Experience Report, CrUX)
    • 定义:这是 Google 用来了解真实世界网站性能的数据源,也是其 SEO 排名算法中性能部分的数据基础。
    • 数据来源
      • 来自全球范围内 已登录 Google 账户 并且 同意同步历史记录 的 Chrome 浏览器用户。
      • 当这些用户访问网站时,他们的浏览器会匿名地收集核心 Web 指标等性能数据,并将其发送回 Google。
    • 覆盖范围
      • 只包含 公开的、有足够访问量 的网站(通常是排名前几百万的网站)。不包含内网站点或本地开发环境。
  • CrUX 数据的特点
    • 现场数据 (Field Data):是真实用户在各种网络和设备条件下的实际体验。
    • 匿名和公开:数据经过匿名化处理,并且是公开的,任何人都可以查询自己或竞争对手网站的数据。
    • 28 天滚动平均值:你看到的数据是过去 28 天数据的滚动聚合值,而不是某一天的实时数据。
    • 可访问性:可以通过 Google BigQuery、API 以及各种第三方工具进行查询。
  • 访问 CrUX 数据的工具
    1. Google BigQuery:功能最强大,但需要 SQL 知识,且不当查询可能会产生成本。
    2. Request Metrics Speed Check (讲师自己开发的工具):
      • 一个简单的 Web 工具,可以快速查询任何域名的 CrUX 分数。
      • 可以用它来进行 竞争对手分析,例如比较 target.comwalmart.com 的性能分数。
    3. PageSpeed Insights (pagespeed.web.dev)
      • Google 官方工具,同时展示两种数据
      • 顶部 显示来自 CrUX 的 现场数据 (Field Data),告诉你“真实用户正在经历什么”。
      • 下方 是一个 实验室数据 (Lab Data) 报告,本质上是在 Google 服务器上运行的一次 Lighthouse 测试。
      • 重要结论现场数据比实验室数据更重要。如果现场数据显示你的网站很慢,即使实验室测试得分 100,你的网站在 Google 眼中仍然是慢的,因为实验室测试未能代表真实用户的情况。

24-using-webpagetest-org

  • WebPageTest (webpagetest.org)
    • 一个非常强大且历史悠久的第三方 Web 性能测试工具(非 Google 出品)。
    • 提供了比 PageSpeed Insights 更高级、更精细的控制。
  • WebPageTest 的核心优势
    1. 精确的测试环境控制
      • 测试地点:可以从全球各地的服务器发起测试(例如:爱尔兰、弗吉尼亚等)。
      • 浏览器选择:可以选择不同的浏览器进行测试(Chrome, Edge, Firefox 等)。
      • 连接类型:可以模拟各种网络连接(如 LTE, Cable, 3G 等)。
      • 设备和屏幕尺寸:可以指定测试设备和屏幕分辨率。
    2. 丰富的测试结果和交互性
      • 测试过程需要排队等待,因为它是在真实的虚拟机上运行一个真实的浏览器实例。
      • 结果页面提供了详细的摘要信息,包括各项性能指标。
      • 最强大的功能是交互式瀑布图
        • 你可以点击瀑布图中的任何一个请求。
        • 查看该请求的完整细节,包括请求/响应头、协议、连接 ID 等。
        • 这对于诊断来自特定地区或特定网络条件下可能出现的性能问题非常有帮助。
  • WebPageTest 的适用场景
    • 当你需要模拟一个特定用户场景(例如,一个在爱尔兰使用 LTE 网络的 Edge 浏览器用户)并深入分析其网络请求时,WebPageTest 是无与伦比的工具。
    • 它能帮助你复现和诊断那些在本地测试中难以发现的、与地理位置或网络相关的性能问题。

25-real-user-monitoring

  • 超越实验室工具:为什么需要真实用户监控?
    • 本地工具(如 Lighthouse)提供的是 实验室数据 (Lab Data)
    • 公开数据(如 CrUX)虽然是 现场数据 (Field Data),但存在限制:
      • 不包含私有网站或未登录的页面。
      • 无法深入查看导致性能差的具体原因(例如,无法查看导致某个用户 CLS 差的瀑布图)。
    • 真实用户监控 (Real User Monitoring, RUM) 工具解决了这些问题。
  • 什么是 RUM?
    • RUM 是指通过在你自己的网站上安装一个脚本,来收集 所有 真实用户的性能数据,并将这些数据发送到你自己的分析平台。
  • CrUX vs. RUM 的对比
特性Chrome 用户体验报告 (CrUX)真实用户监控 (RUM)
数据来源仅限已登录的 Chrome 用户你网站的所有访客
覆盖范围仅限公开的热门网站你的任何网站(公开、私有、内网)
隐私性匿名且公开私有,数据属于你
实时性28 天滚动平均值实时数据
可钻取性聚合数据,无法深入可钻取至单次用户会话
自定义标准指标可捕获自定义指标
  • RUM 工具的工作原理
    • 通常,你需要在你的网站中安装一个 JavaScript 代理(agent)。
    • 这个代理在后台使用 PerformanceObserver 等 API 来捕获性能指标。
    • 然后将捕获到的数据发送到一个数据收集和报告服务。
  • RUM 工具的分类
    • 企业级 RUM 工具:功能强大,价格昂贵。适用于大型企业。
      • 示例:Akamai mPulse (行业先驱), Dynatrace, AppDynamics, Datadog, Sentry。
    • 项目级 RUM 工具:更专注,价格更亲民。适用于中小型团队或单个项目。
      • 示例:Request Metrics (讲师自己的产品), SpeedCurve, RUMVision, Pingdom, Raygun。

26-request-metrics-monitoring-tool

  • Request Metrics 作为 RUM 工具的案例演示
    • 免责声明:这是讲师自己开发的产品。
  • RUM 工具能提供什么 CrUX 无法提供的数据和能力?
    1. 数据的细分和过滤 (Slicing and Dicing)
      • 你可以根据任意维度过滤你的性能数据。
      • 例如:只看来自“美国”、使用“宽带连接”的用户的核心 Web 指标;或者只看来自“美国”、使用“慢速蜂窝网络”的用户的指标,以了解不同用户群体的体验差异。
    2. 按 URL 和元素进行聚合分析
      • 你可以查看特定 URL(例如 /speedcheck 页面)的性能表现。
      • 进一步下钻,可以看到在该页面上,哪个元素 最常成为 LCP 元素,帮助你精准定位优化目标。
    3. 钻取到单次用户会话 (Drill-down to Individual Sessions)
      • 这是 RUM 最强大的功能。你可以查看某一个真实用户的单次访问记录。
      • 就像为每一个用户都运行了一次 WebPageTest
      • 你可以看到该用户的浏览器、操作系统、地理位置。
      • 查看完整的加载时间线、瀑布图、LCP 元素、甚至请求的 HTTP 头信息。
      • 这使得复现和解决复杂、偶发的性能问题成为可能。
  • 问答环节
    • 最好的企业级工具是哪个? 如果不考虑预算,Akamai mPulse 是行业标杆,其开源的 Boomerang.js 代理是许多其他工具的基础。
    • RUM 工具如何设置? 通常是在你的网站中安装一个 JavaScript 包,它会自动收集数据并发送到 RUM 服务提供商。它可以监控任何网站,包括需要登录的私有网站,只要用户的浏览器能访问互联网即可。
    • RUM 工具可以捕获自定义指标吗? 可以。大多数 RUM 工具支持通过其 SDK 发送自定义事件和指标,让你能够监控业务特定的性能数据。
  • 结论
    • 如果你对网站的性能是认真的,那么部署一个 RUM 工具是必不可少的。

27-how-fast-should-your-site-be

  • 设定性能目标:多快才算足够快?
    • “快”是一个主观概念,取决于你的应用类型和用户群体。
    • 例如,一个开发者贴纸商店需要非常快,因为用户是没耐心的开发者。而一个报税或贷款申请网站,用户愿意等待更长时间,因为任务价值更高。
  • 等待的心理学 (Psychology of Waiting)
    • Web 性能不仅仅是技术问题,也是心理学问题。
    • 来自上世纪 60 年代排队研究的启示
      1. 人们渴望开始=:用户急于完成他们来网站的目的。
      2. 无聊的等待感觉更长=:这就是为什么加载界面会使用有趣的动画来分散用户的注意力。
      3. 焦虑的等待感觉更长:对结果感到紧张会拉长主观等待时间。
      4. 原因不明的等待感觉更长:用户不理解为什么慢,会感到更沮丧。
      5. 时长不确定的等待感觉更长:没有进度条或反馈,等待会变得难以忍受。
      6. 人们愿意为高价值的东西等待更久
    • 反向案例:TurboTax 的“刻意减速”
      • 在提交税表后,TurboTax 会显示一个“正在检查所有细节”的动画,这实际上是一个固定的等待时间,背后并没有进行复杂的计算。
      • 原因:他们的研究表明,对于报税这样高价值、高风险的操作,适当的“慢”会让用户感觉系统更可靠、更值得信赖。

28-determining-performance-goals

  • 谁来决定“多快才够快”?
    • 答案:不是你。而是由三个外部因素决定。
  • 决定性能目标的三个因素
    1. 用户体验 (通过业务指标反映)
      • 你需要将 Web 性能指标与你的 业务指标 (Business Metrics) 关联起来。
      • 业务指标示例:跳出率 (Bounce Rate), 会话时长 (Session Time), 购物车添加率, 转化率 (Conversion Rate) 等。
      • 分析方法:在图表上将性能指标(如 LCP, CLS)和业务指标(如跳出率)并列展示,寻找它们之间的 相关性 (Correlation)
        • 示例发现:CLS 升高时,会话时长下降;LCP 变慢时,跳出率上升。
      • 重要警告:相关不等于因果 (Correlation is not causation)
        • 经典的例子是“全球平均气温”和“海盗数量”之间的负相关。虽然数据上相关,但两者没有因果关系。
        • 你需要基于数据进行假设和验证,而不是盲目下结论。
    2. 你的竞争对手 (Your Competitors)
      • 用户是否能感知到你和竞争对手之间的速度差异?
      • 韦伯定律 (Weber's Law) / 20%规则:通常,两个事物之间需要有大约 20% 的差异,人们才能明显地感知到。
      • 应用于性能:如果你的网站只比竞争对手快 4%,用户是感觉不到的。但如果你快了 57%,这种差异就会非常明显,成为你的竞争优势。
      • 你可以使用 CrUX 数据来对比你和竞争对手的核心 Web 指标。
    3. 你的 SEO 页面排名 (Your SEO Page Rank)
      • 这是最直接、最不容商量的目标。
      • 为了避免在 Google 搜索排名中受到性能惩罚,你的核心 Web 指标 必须 达到 Google 设定的“良好”阈值。
      • LCP: ≤ 2.5 秒
      • CLS: ≤ 0.1
      • INP: ≤ 200 毫秒
  • 总结
    • 你的性能目标应综合考虑这三方面:用户行为(业务指标)、市场竞争(竞争对手)和搜索引擎要求(SEO)。

29-understanding-your-users

  • 了解你的用户:你为谁构建网站?
    • 你在开发环境中看到的,很可能与你的真实用户所经历的大相径庭。
  • 全球 Web 用户统计数据 (来自 StatCounter)
    • 设备份额
      • 移动设备 (Mobile): 62%
      • 桌面设备 (Desktop): 36%
      • 结论:移动端是主流。这就是为什么 Google 推行“移动优先索引”。
    • 屏幕尺寸
      • 分布非常分散,没有绝对的主流。
      • 最大的群体之一是 360 像素宽 的小屏幕设备,这比绝大多数开发者使用的屏幕要小得多。
    • 操作系统份额
      • Android: 71%
      • iOS: 27%
      • Windows: 71% (在桌面端)
    • 关键洞察
      • 全球移动用户中,绝大多数使用 Android。
      • 全球 Android 手机的平均售价约为 $286 美元
      • 这意味着,互联网上的“平均用户”正在使用一台 廉价、性能较低的 Android 手机
  • 全球网络速度 (来自 Speedtest.net)
    • 移动网络平均速度:60Mbps 下载 / 11Mbps 上传 / 27ms 延迟。
    • 宽带网络平均速度:90Mbps 下载 / 40Mbps 上传 / 10ms 延迟。
    • 重要提示:这些是全球平均值,不同地区的差异巨大。有些地区网速超过 250Mbps,而有些地区则低于 10Mbps。
  • 你需要了解你自己的用户
    • 全球数据提供了宏观背景,但你最需要的是 你自己网站的用户数据
    • 使用分析工具或 RUM 工具来回答以下问题:
      • 你的用户主要来自哪个国家?
      • 他们使用什么操作系统和浏览器?
      • 他们最常见的屏幕尺寸是什么?
    • 只有了解了这些,你才能配置出最接近真实用户场景的测试环境,从而做出有意义的性能优化。

30-improving-performance

  • 改进 Web 性能的总体策略
    • 设定明确目标:在开始优化之前,你需要根据你的用户、行业和竞争对手,明确你的性能目标是什么。
    • 聚焦关键问题
      1. 基于真实用户数据:使用你的 RUM(真实用户监控)工具或 CrUX(Chrome 用户体验报告)数据来识别性能瓶颈,而不是仅仅依赖 Lighthouse 分数。
      2. 先解决最差的指标:如果多个指标都不理想,从最糟糕的那个开始着手。
      3. 从最简单的修复方案开始:不要追求完美,一次性实施所有优化策略。通常,几个简单的修复就能带来显著的提升。有时候,“足够快”就可以了。
    • 核心原则:“少做”
      • 计算机的速度受限于物理定律(如光速),你无法使其变得更快。
      • 优化的本质是 减少在用户操作和目标指标发生之间所需完成的工作量
      • 这可能意味着:
        • 减少页面上的元素。
        • 减小文件大小(字节数)。
        • 在适当的地方进行缓存。
  • 课程结构
    • 接下来将逐个分析每个性能指标,并提供具体的优化策略,这部分内容可以作为日后解决特定性能问题时的参考索引。

31-baseline-time-to-first-byte

  • 改进首字节时间 (Time to First Byte, TTFB)
    • 回顾 TTFB:衡量服务器响应请求的速度。
    • TTFB 的重要性:TTFB 是所有后续加载指标的基础。缓慢的 TTFB 会直接导致缓慢的 FCP 和 LCP。因此,优化 TTFB 是性能优化的第一步。
  • 建立性能基线
    • 测试环境设置
      1. 部署地理位置:将示例应用的服务器部署在阿姆斯特丹(荷兰),而测试者位于明尼阿波利斯(美国),以模拟真实的地理网络延迟。
      2. Chrome DevTools 配置
        • 网络节流:使用自定义的“美国咖啡店 WiFi”配置(10Mbps 下载/4Mbps 上传/50ms 延迟)。
        • CPU 节流:设置为 6 倍降速。
    • 测量初始 TTFB
      • 使用 Chrome 的性能面板(Performance Panel)或 Web Vitals 扩展程序进行测量。
      • 初始测量的 TTFB 为 1.775 秒,这是一个非常差的成绩,主要原因是一个完全未经优化的网站,加上跨大西洋的网络延迟和人工节流。
    • 确定优化目标
      • 参考你的 RUM 或 CrUX 数据,查看 p75(75 百分位)的 TTFB。
      • Google 建议的目标是 小于 800 毫秒 (0.8 秒)

32-enabling-gzip-brotli-compression

  • TTFB 优化策略一:HTTP 资源压缩
    • 目的:通过压缩减少传输的字节数,从而加快下载速度。
    • 对象:主要针对文本文件,如 HTML、CSS 和 JavaScript。
    • 常用压缩算法
      1. Gzip:一种广泛支持的经典压缩算法。
      2. Brotli (br):由 Google 开发的新一代算法,通常比 Gzip 有更高的压缩率。
    • 压缩效果示例
      • 一个 1.1MB 的 HTML 文件,使用 Gzip 可以压缩到 282KB(原大小的 25%),而使用 Brotli 可以压缩到 20KB(原大小的 1.7%)。
  • 压缩的工作原理(内容协商)
    1. 浏览器发起请求:浏览器在请求头中通过 Accept-Encoding: gzip, deflate, br 告诉服务器它支持哪些压缩算法。
    2. 服务器响应:服务器选择一种它支持且浏览器也支持的算法(通常优先选择 Brotli),对响应体进行压缩,并通过响应头 Content-Encoding: br 告知浏览器使用了哪种算法。
  • 实施与效果
    • 实施:在服务器配置中启用 Gzip 和/或 Brotli 压缩。这通常是一个简单的开关或几行配置代码。
    • 效果:启用 Gzip 后,一个 33KB 的 HTML 文件被压缩到了 6KB。
    • 注意:对于非常小的文件,压缩可能没有效果,甚至会因为算法开销而使文件稍微变大。Brotli 在大文件上的优势比在小文件上更明显。

33-efficient-protocols

  • TTFB 优化策略二:使用更高效的协议
    • 背景:传统的 HTTP/1.1 协议效率低下,每个请求都需要建立和拆除一个 TCP 连接,导致网络非常“健谈”(chatty)。
  • 协议的演进
    1. HTTP/2
      • 核心改进多路复用 (Multiplexing)。它允许在一个 TCP 连接上同时传输多个请求和响应,大大减少了连接建立的开销。
      • 结果:显著提升了加载含有多个小资源的页面的速度。
    2. HTTP/3
      • 核心改进基于 QUIC 协议 (UDP)
        • 放弃了 TCP,改用基于 UDP 的 QUIC 协议。
        • TCP vs. UDP:TCP 是可靠连接,每次数据传输都需要对方确认(ACK),这会引入延迟。UDP 则不管对方是否收到,直接“尽力而为”地发送数据,速度更快。
        • 0-RTT 连接建立:HTTP/3 可以大大减少建立安全连接(TLS)所需的往返次数,进一步降低延迟。
  • 性能对比研究
    • 结果:从 HTTP/1.1 到 HTTP/2,再到 HTTP/3,性能有显著的、阶梯式的提升。在不同类型的网站上,HTTP/3 的速度大约是 HTTP/1.1 的两倍。

34-using-http-2-http-3

  • 实施 HTTP/2 和 HTTP/3 的挑战
    1. 本地开发困难:配置复杂的环境,尤其是 HTTPS 证书。
    2. 需要 HTTPS (TLS):HTTP/2 和 HTTP/3 都强制要求使用加密连接。
    3. 网络防火墙:HTTP/3 使用的 UDP 协议可能需要特殊的防火墙规则。
    4. 工具支持:一些旧的命令行工具(如标准版的curl)尚不支持 HTTP/3。
    • 结论:这些协议的启用通常是在 生产环境的服务器或代理 上完成的,而不是在本地开发中。
  • 演示:通过代理启用 HTTP/3
    • 使用 Caddy(一个现代、易于使用的 Web 服务器/反向代理)作为前端代理。
    • 工作流程
      1. 浏览器首次通过 HTTPS 访问网站,使用 HTTP/2 进行连接。
      2. 服务器在响应头中通过 Alt-Svc header 告诉浏览器:“嘿,我也支持 HTTP/3”。
      3. 浏览器接收到这个信息后,后续的请求就会自动切换到更快、更高效的 HTTP/3 (QUIC) 协议。
    • 效果:启用 HTTP/3 后,所有资源都通过单一的、高效的 UDP 连接进行流式传输,减少了网络延迟。

35-host-capacity-proximity

  • TTFB 优化策略三:主机容量和邻近性
  • 1. 调整主机容量 (Right-sizing your host)
    • 目的:确保你的服务器有足够的计算资源(CPU、内存)来快速处理请求。
    • 场景:如果你的服务器在响应请求时需要进行复杂的计算或数据库查询,资源不足会导致处理时间过长。
    • 演示
      • 原始代码中人为设置了 1 秒(1000 毫秒)的服务器处理延迟,模拟一个缓慢的后端。
      • 将延迟减少到 50 毫秒,模拟一个经过优化的、响应迅速的后端。
      • 效果:部署更改后,TTFB 从 1 秒多大幅降低到 210 毫秒 (0.21 秒),进入了“良好”范围。这一改变也带动了 FCP 和 LCP 的显著改善。
  • 2. 优化主机邻近性 (Putting your host close to the user)
    • 问题:物理距离 = 延迟
      • 数据在网络上传输需要时间,这个时间受限于光速。
      • 从明尼阿波利斯(美国)到阿姆斯特丹(荷兰)的往返延迟大约是 117 毫秒,这是不可逾越的物理限制,是每个请求的“延迟税”。
    • 解决方案:内容分发网络 (Content Delivery Network, CDN)
      • 工作原理
        1. CDN 在全球各地部署了大量的边缘服务器 (Edge Servers)。
        2. 当用户请求你的网站时,请求会被路由到离他们最近的边缘服务器。
        3. 如果边缘服务器有内容的缓存,它会直接返回给用户,避免了长距离的跨国传输。
        4. 如果缓存未命中 (Cache Miss),边缘服务器会代表用户向你的源服务器请求内容,获取后再返回给用户并缓存起来,供后续用户使用。
    • 演示
      • 使用 BunnyCDN 服务。
      • 首次请求(冷缓存):耗时 308 毫秒,因为 CDN 需要回源到阿姆斯特丹获取内容。
      • 再次请求(热缓存):耗时仅 63 毫秒,因为内容直接从位于美国的 CDN 边缘节点返回。
    • 结论:CDN 是解决地理延迟、提升全球用户访问速度最有效的方法。
  • TTFB 优化总结
    • 结合所有优化(压缩、HTTP/3、后端优化、CDN)后,最终的 TTFB 达到了惊人的 20 毫秒 (0.02 秒)
    • 重要启示:所有这些优化都属于 运维 (Ops) 层面,没有修改任何一行 HTML, CSS 或 JavaScript 代码。很多时候,巨大的性能提升来自于改进基础设施,而不是修改前端代码。

36-baseline-first-contentful-paint

  • 改进首次内容绘制 (First Contentful Paint, FCP)
    • 回顾 FCP:页面渲染出 任何 内容所需的时间。
    • 与 TTFB/LCP 的关系TTFB → FCP → LCP。优化 FCP 也能帮助优化 LCP。
  • 建立 FCP 基线
    • 在应用了所有 TTFB 优化之后,重新进行性能测试。
    • 当前的 FCP 基线为 871 毫秒
    • 已经达到了 Google 的“良好”标准(< 1.8 秒),但仍有优化空间。
  • 优化前需自问:这真的是问题吗?
    • 在投入时间和精力之前,请检查你的 RUM 或 CrUX 数据。如果真实用户的 FCP 已经很好,也许你应该把精力放在其他更需要优化的指标上。
  • FCP 的三大优化策略
    1. 移除序列链 (Removing sequence chains)
    2. 预加载资源 (Preloading resources)
    3. 懒加载资源 (Lazy loading resources)

37-removing-sequence-chains

  • FCP 优化策略一:移除或扁平化依赖链
    • 问题:资源加载存在依赖关系时,会形成一个串行的“请求链”,下一个资源的下载必须等待上一个资源下载并解析完成,这会大大延长关键渲染路径。
  • 常见的请求链
    1. CSS @import:
      • style.css 导入 base.cssbase.css 又导入 colors.css...
      • 浏览器必须按顺序下载和解析这些文件,导致加载时间累加。
    2. CSS 中的字体文件
      • 浏览器必须先下载 CSS 文件,解析后发现其中引用了某个字体文件,然后才开始下载该字体。
    3. JavaScript 模块导入 (ESM import)
      • main.js 导入 utils.js。浏览器在执行main.js时,会暂停并去下载utils.js
    4. JavaScript 动态注入脚本
      • 一个脚本通过创建<script>标签并插入到 DOM 中来加载另一个脚本。
  • 解决方案:使用打包工具 (Bundler)
    • 目的:在 构建时 (build time) 而非 运行时 (run time) 解析这些依赖关系,并将所有需要的文件合并成一个或几个优化的文件。
    • 常用工具:Webpack, Rollup, Vite 等。
    • 演示
      • 原始 CSS 文件通过@import形成了多层依赖链。
      • 使用 Lightning CSS(一个快速的 CSS 打包和压缩工具)将所有 CSS 文件合并成一个 styles.bundle.css 文件。
      • 效果:在 HTML 中引用这个打包后的文件,浏览器就可以一次性发起所有 CSS 的请求,消除了串行等待,使并行加载成为可能,从而缩短了整个加载时间。

38-preloading-resources

  • FCP 优化策略二:预加载关键资源
    • 目的:告诉浏览器尽早开始下载那些对首次渲染至关重要的资源,即使浏览器还没有在解析过程中自然地发现它们。
    • 关键资源示例:渲染阻塞的 CSS、首屏需要的字体或图片。
  • 如何预加载
    • 使用 <link rel="preload"> 标签在 HTML 的 <head> 中。
    • 示例:预加载字体
      • 问题:从 Google Fonts 加载字体通常需要先下载一个 CSS 文件,然后浏览器才从该 CSS 文件中发现字体的 URL 并开始下载。
      • 优化前:Google Fonts 建议使用 <link rel="preconnect">,这只能预先建立连接,但不能预先下载文件。
      • 优化后
        <link
          rel="preload"
          href="path/to/font.woff2"
          as="font"
          type="font/woff2"
          crossorigin
        />
        
        • href: 资源的 URL。
        • as: 必须指定资源类型(如 font, style, script, image)。
        • crossorigin: 当从不同源加载字体或进行 fetch 时是必需的。
    • 效果:浏览器在解析 HTML 的早期阶段就立即开始下载字体文件,与 CSS 文件的下载并行进行,而不是等待 CSS 下载解析完毕后再开始,从而大大缩短了字体可用所需的时间。
  • 最佳实践
    • 不要滥用preload:只预加载那些确定在当前页面加载初期就必须使用的关键资源。预加载过多的资源会抢占带宽,反而可能延误更重要的资源的加载。
    • 自托管字体:与其预加载 Google 服务器上 URL 不稳定的字体文件,更好的做法是将字体文件下载到你自己的服务器上(自托管),然后从你的 CDN 进行预加载。这样不仅 URL 稳定,而且通常速度更快。

39-lazy-loading-resources

  • FCP 优化策略三:延迟加载非关键资源 (特别是 JavaScript)
    • 问题:JavaScript 是解析器阻塞 (Parser Blocking)的
      • 当浏览器在解析 HTML 时遇到一个普通的<script>标签,它会:
        1. 暂停 HTML 解析。
        2. 开始下载该脚本。
        3. 下载完成后,立即执行 该脚本。
        4. 执行完毕后,才继续解析剩余的 HTML。
      • 这个过程会阻塞主线程,延迟页面的首次渲染(FCP)。
  • 解决方案:使用 asyncdefer 属性
    1. 普通脚本 <script src="..."> (无属性):
      • 下载和执行都会阻塞 HTML 解析。
    2. async 脚本 <script async src="...">:
      • 异步下载:下载过程不阻塞 HTML 解析。
      • 下载后立即执行:一旦下载完成,会 立即暂停 HTML 解析来执行脚本。
      • 问题:执行时机不确定,可能仍然会阻塞渲染。如果多个async脚本存在,它们的执行顺序也不确定,会产生“竞争条件”。
    3. defer 脚本 <script defer src="...">: ( 推荐使用 )
      • 异步下载:下载过程不阻塞 HTML 解析。
      • 延迟执行:脚本会一直等到 整个 HTML 文档解析完毕 (在 DOMContentLoaded 事件触发之前) 才按顺序执行。
      • 优点:既不阻塞页面渲染,又能保证脚本的执行顺序,是绝大多数场景下的最佳选择。
  • 其他要点
    • ES 模块 (<script type="module">):默认行为就是 defer
    • 脚本位置 (<head> vs. <body>末尾):在有了defer之后,脚本放在哪里变得不那么重要了。放在<head>并使用defer通常是最好的做法,因为它允许浏览器更早地发现并开始下载脚本。
  • 演示与效果
    • 实施:为页面中所有非关键的 JavaScript 文件(如处理用户交互的scripts.js和处理促销横幅的promo.js)添加 defer 属性。
    • 效果
      • JavaScript 的下载和执行不再阻塞关键渲染路径。
      • FCP 不再需要等待 JS 加载完成,它现在只取决于 CSS 和字体文件的加载。
      • 最终,FCP 优化到了 624 毫秒
  • FCP 优化总结
    1. 移除序列链:通过打包工具合并 CSS 和 JS,消除串行请求。
    2. 预加载关键资源:使用<link rel="preload">让关键字体和 CSS 尽早开始下载。
    3. 延迟加载非关键 JavaScript:为所有非必须立即执行的脚本添加defer属性。

40-baseline-for-largest-contentful-paint

  • 改进最大内容绘制 (Largest Contentful Paint, LCP)
    • 回顾 LCP: 页面中最大(通常也是最重要)的元素可见所需的时间。这是影响 SEO 的关键核心 Web 指标。
    • 与 TTFB/FCP 的关系: 之前对 TTFB 和 FCP 的优化已经自动改善了 LCP 的基础。
  • LCP 的构成部分
    1. 资源加载延迟 (Resource Load Delay): 从页面开始加载到 LCP 元素的资源(通常是图片)开始下载的时间。之前的 TTFB 和 FCP 优化主要就是为了缩短这个延迟。
    2. 资源加载时长 (Resource Load Duration): LCP 元素资源本身下载所需的时间。这是本节优化的重点。
    3. 元素渲染延迟 (Element Render Delay): LCP 资源下载完成后,到它真正被渲染到屏幕上的时间。通常很短,除非有大量阻塞的 JS。
  • 建立 LCP 基线
    • 重新运行性能测试,查看 LCP。
    • 当前的 LCP 基线是 14.26 秒,非常差。
    • 通过查看瀑布图和分析 Web Vitals 扩展的控制台输出,可以确定 LCP 元素是 hero-mobile.png,一个 1.7MB 的大图片。
    • 注意:LCP 值是动态的。页面可能会先渲染一个较小的元素作为 LCP,等一个更大的图片加载完成后,LCP 值会被更新为这个更大的图片的时间。
  • 确定优化目标
    • Google 的“良好”标准是 小于 2.5 秒。当前 14 秒的成绩显然需要优化。
  • LCP 的三大优化策略
    1. 更多的懒加载 (Lazy Loading)
    2. 主动加载 (Eager Loading)
    3. 图片优化 (Optimizing Images)

41-lazy-loading-images

  • LCP 优化策略一:更多的懒加载
    • 目的: 移除关键路径上不必要的资源,特别是那些非 LCP 的图片。
  • 问题分析
    • 瀑布图显示,在 LCP 图片(Hero 图片)下载的同时,还有很多其他不那么重要的图片(如 logo、产品缩略图)也在下载。
    • 这些图片会与 LCP 图片竞争网络带宽,减慢了 LCP 图片的下载速度,从而增加了资源加载时长 (Resource Load Duration)
  • 解决方案:loading="lazy"
    • 工作原理: 通过给<img><iframe>标签添加 loading="lazy" 属性,可以告诉浏览器延迟加载这些资源,直到它们即将进入用户的视口。
    • 实施:
      1. 批量添加: 对项目中的所有图片都添加 loading="lazy"
      2. 移除关键图片的 lazy: 手动找到 LCP 图片(在这个例子中是 hero-desktop.pnghero-mobile.png),并将它们的 loading="lazy" 属性移除
    • 效果:
      • 浏览器会优先下载没有 loading="lazy" 属性的图片(即我们的 LCP 图片)。
      • 其他被标记为 lazy 的图片,即使在 HTML 中出现得更早,也会被推迟加载,直到浏览器有空闲的网络资源。
      • 这样就确保了 LCP 图片能够更快地开始并完成下载。
  • 懒加载的原则
    • 首屏以下 (Below the fold) 的图片: 应该 总是 懒加载。
    • 首屏之内 (Above the fold) 但非 LCP 的图片: 也应该 懒加载,以避免与 LCP 元素竞争资源。

42-eager-loading

  • LCP 优化策略二:主动加载 (Eager Loading)

    • 目的: 不仅要延迟加载非关键资源,还要明确告诉浏览器“这个 LCP 资源非常重要,请尽快加载它!”
  • 主动加载的方法

    1. <link rel="preload"> (已在 FCP 部分介绍过)

      • 这是告诉浏览器“我确定这个页面需要这个图片,请立即以高优先级开始下载它”的最直接方式。
      • 优点: 浏览器支持广泛。
      <link rel="preload" href="/path/to/hero.png" as="image" />
      
    2. fetchpriority="high" 属性

      • 一个较新的 HTML 属性,可以添加到 <img>, <script>, <link> 等标签上。
      • 工作原理: 它不是让浏览器更早地 发现 资源,而是在资源被发现后,告诉浏览器调度器在网络队列中为其分配 更高的优先级
      • 优点: 语法更简洁,可以直接写在<img>标签上。
      • 缺点: Firefox 尚不支持。但对于影响 SEO 的场景(主要看 Chrome),这是有效的。
  • 实施与效果

    • 实施:
      1. 为 LCP 图片 <img> 标签添加 fetchpriority="high"
      2. <head> 中为 LCP 图片添加 <link rel="preload">
    • 效果:
      • 结合 preload 和 fetchpriority,LCP 图片的请求在瀑布图上被尽可能地移到了最左边,即页面加载的最早阶段。
      • 资源加载延迟被降到了最低。
      • 现在,LCP 的瓶颈完全在于图片本身的下载时间,即图片文件太大了。

43-image-formats

  • LCP 优化策略三:图片优化
    • 背景: 即使我们以最高优先级、最早的时间开始加载 LCP 图片,如果图片本身有几 MB 大,下载仍然会很慢。所以必须减小图片文件的大小。
    • 注意: HTTP 压缩(Gzip, Brotli)对图片文件效果甚微,因为图片本身已经是压缩格式了。
  • 1. 选择现代图片格式
    • 传统格式: JPEG (适用于照片), PNG (适用于插图、透明背景)。
    • 现代格式:
      • WebP: 由 Google 开发,支持度和压缩率都很好,通常比 JPEG/PNG 小很多。
      • AVIF: 比 WebP 更新,压缩率可能更高,但支持度稍差。
    • 结论: 在可能的情况下,优先使用 WebPAVIF,它们几乎总能提供比传统格式更好的压缩效果,且文件大小通常能减少一半以上。
    • 优化工具: 如果无法使用现代格式,可以使用像 TinyPNG 这样的工具来无损或有损地压缩 PNG/JPEG 文件,同样能显著减小文件大小。
  • 2. 使用响应式图片 (Responsive Images)
    • 问题: 在小屏幕的移动设备上显示一张为 4K 桌面显示器设计的大图是在浪费带宽。
    • 解决方案: 为不同屏幕尺寸和分辨率提供不同大小的图片版本,让浏览器根据需要选择最合适的一个加载。

44-responsive-images

  • 如何实现响应式图片

    • 使用 HTML <picture><source> 元素,结合 srcset 属性。
    <picture>
      <!-- 移动设备版本 -->
      <source
        media="(max-width: 720px)"
        srcset="/img/hero-mobile-360.png 360w, /img/hero-mobile-720.png 720w"
      />
    
      <!-- 桌面设备版本 -->
      <source
        media="(min-width: 721px)"
        srcset="
          /img/hero-desktop-1440.png 1440w,
          /img/hero-desktop-2800.png 2800w
        "
      />
    
      <!-- 回退方案 -->
      <img src="/img/hero-desktop-2800.png" alt="Hero Image" />
    </picture>
    
    • <picture>: 包裹元素。
    • <source>: 定义一个媒体条件 (media) 和一组图片源 (srcset)。浏览器会从上到下匹配第一个符合条件的<source>
    • media="(max-width: 720px)": 类似于 CSS 媒体查询,定义了该组图片适用的屏幕条件。
    • srcset="... 360w, ... 720w": srcset 属性提供了多个图片 URL,并用 w 描述符标明了每个图片的 固有宽度 (intrinsic width)。浏览器会根据设备的屏幕密度和图片在页面布局中的实际显示大小,智能地选择一个最接近且稍大的图片进行下载。
    • <img>: 必须的、作为回退的默认图片。
  • 接下来: 将会通过实际操作,生成不同尺寸、不同格式的图片,并用<picture>元素来实现响应式加载。

45-optimizing-images

  • 实际操作:自动化图片优化流程
    • 工具: 使用 Node.js 脚本,结合 Jimp (调整尺寸) 和 imagemin (压缩) 库来处理图片。
    • 步骤:
      1. 调整尺寸 (Resizing): ( npm run image:png-resizer )
        • 为项目中的每一张 PNG 图片,生成多个不同宽度的版本(如 360px, 720px, 1024px 等)。
      2. 压缩 (Optimizing): ( npm run image:png-optimize )
        • 对所有尺寸的图片进行无损压缩,去除冗余数据,减小文件大小。
        • 效果:一张 1.5MB 的图片被压缩到了 470KB,视觉上无差异。
      3. 转换为 WebP 格式 (Converting to WebP): ( npm run image:png-to-webp )
        • 将所有压缩后的 PNG 图片转换为 WebP 格式。
        • 效果:470KB 的 PNG 图片转换后变成了 69KB 的 WebP 图片。
      • 最终结果: 一张 1.5MB 的原始图片,经过三步优化,最终变成了一张 69KB 的高质量 WebP 图片,大小减少了 95% 以上。
  • 在代码中应用优化后的图片
    1. 全局替换: 使用 VS Code 的正则表达式搜索替换功能,将所有 .png 的引用批量修改为 .webp
    2. 实现响应式 LCP 图片:
      • 使用 <picture> 元素替换原来的简单 <img> 标签。
      • 为移动端和桌面端分别设置 <source>,并提供多个尺寸的 WebP 图片供浏览器选择。
    3. 一个重要的权衡:
      • 当使用 <picture> 元素时,浏览器需要先解析 HTML,看到媒体查询条件,才能决定加载哪一张图片。
      • 这意味着之前为特定图片 URL 设置的 <link rel="preload"> 会失效,因为在解析<head>时,浏览器还不知道最终会选择哪张图片。我们必须移除这些 preload 标签。
      • 此时,fetchpriority="high" 仍然有用,因为它作用于浏览器做出选择后的那个图片请求。
  • 最终效果
    • 部署所有图片优化和响应式代码后,重新进行性能测试。
    • LCP 从 14 秒大幅降低到了 454 毫秒 (0.45 秒)
    • 所有核心 Web 指标现在都进入了“良好”(绿色)范围。

46-caching

  • 改进回头客体验:缓存 (Caching)
    • 到目前为止的优化主要针对首次访问的用户。缓存则能极大地提升 再次访问 用户的加载速度。
  • 缓存的类型
    1. 服务器/CDN 缓存: 之前通过 CDN 实现,内容被缓存在离用户更近的边缘服务器上。
    2. 浏览器响应缓存: 将资源直接缓存在用户的浏览器中。
  • 浏览器缓存的两种机制
    1. 验证缓存 (Validation Caching):
      • 工作原理:
        • 服务器在首次响应时,提供 ETag (文件内容的唯一标识) 和 Last-Modified (最后修改日期) 头。
        • 用户再次请求该资源时,浏览器会带上 If-None-Match (ETag) 和 If-Modified-Since (日期) 头去询问服务器。
        • 如果服务器发现文件未改变,会返回一个 304 Not Modified 状态码,响应体为空。浏览器则直接使用本地缓存。
      • 优点: 确保用户总能获取最新版本。
      • 缺点: 仍然需要发起一次网络请求(尽管响应很小)。
    2. 强缓存 (Strong Caching):
      • 工作原理:
        • 服务器在首次响应时,提供 Cache-Control: max-age=<seconds>Expires: <date> 头。
        • 这相当于告诉浏览器:“在接下来的 max-age 秒内/在 Expires 日期之前,你 不需要 再来问我,直接用本地的就行。”
        • 在缓存有效期内,浏览器 不会发起任何网络请求,直接从磁盘或内存中加载资源,速度极快(0ms)。
      • 优点: 速度最快,无网络延迟。
      • 缺点: 如果在缓存有效期内更新了文件,用户将无法获取到最新版本。
  • 缓存失效 (Cache Busting)
    • 为了解决强缓存的更新问题,最佳实践是在文件名中加入内容的哈希值或版本号(例如 scripts.a1b2c3d4.js)。
    • 当文件内容改变时,文件名也会改变,浏览器会将其视为一个全新的资源并发起请求。
    • 这使得我们可以放心地为静态资源(JS, CSS, 图片)设置非常长的缓存时间(如一年)。

47-enabling-caching-headers

  • 实施缓存
    • 操作: 在服务器配置中启用缓存相关的 HTTP 响应头。
    • 演示:
      • 在服务器端代码中添加逻辑,为静态资源同时返回 ETag/Last-ModifiedCache-Control 头。
    • 效果:
      • 首次访问: 资源正常下载,但浏览器会记住缓存头。
      • 再次访问 (缓存有效期内):
        • 如果在 DevTools 中勾选了 "Disable cache",会触发 验证缓存,看到 304 Not Modified 响应。
        • 如果取消勾选 "Disable cache",会触发 强缓存,在网络面板中看到 "(from memory cache)" 或 "(from disk cache)",加载时间为 0ms,没有实际的网络请求。
    • 结论: 配置正确的缓存策略是提升回头客体验最简单、最有效的方法之一。

48-layout-size-hints

  • 改进累积布局偏移 (Cumulative Layout Shift, CLS)

    • 回顾 CLS: 测量页面加载过程中,可见元素位置发生非预期移动的程度。
    • 问题引入: 之前的 LCP 优化中,我们大量使用了懒加载,这可能会导致图片在加载完成后突然出现,将下方内容推开,从而产生 CLS。
  • 建立 CLS 基线

    • 当前版本的网站有严重的 CLS 问题,得分为 2.56 (远超“良好”标准 ≤ 0.1)。
    • 主要原因:
      1. 懒加载的图片(如页眉的 logo、产品图)加载完成后,没有预留空间,导致布局移动。
      2. 一个动态插入的促销横幅 (Promo Banner) 从顶部推入,将整个页面内容向下推。
  • CLS 的核心优化策略:提供尺寸提示 (Layout Size Hints)

    • CLS 问题的根源是浏览器在加载某些元素(特别是图片或动态内容)之前,不知道它们将占据多大的空间。
    • 解决方案就是 提前告诉浏览器这些元素的尺寸
  • 修复 CLS 的具体方法

    1. 为图片提供 widthheight 属性:

      <img src="..." width="500" height="500" loading="lazy" />
      
      • 这是最简单也最重要的方法。即使图片是懒加载的,浏览器也能根据这两个属性计算出 宽高比 (Aspect Ratio),从而在图片加载完成前预留出正确大小的空间。
      • 注意: 只写数字,不要带 px 单位。
    2. 为动态插入的内容预留空间:

      • 针对促销横幅这类问题,有两种常见方案:
        • 方案 A:固定空间 (Fixed Space)
          • 在 CSS 中为横幅的容器设置一个固定的 height (或 min-height)。页面加载时,这个空间就是空的,等横幅内容加载进来后,正好填满,不会推移其他内容。
          • 缺点: 可能不够吸引眼球。
        • 方案 B:浮动覆盖 (Overlay)
          • 使用 CSS 的 position: absoluteposition: fixed 将横幅定位为浮动在页面内容之上。当它出现时,不会影响下方内容的布局。
          • 优点: 更具视觉冲击力。
  • 操作与效果

    • 实施:
      1. 为所有首屏内的懒加载图片添加 widthheight 属性。
      2. 修改促销横幅的 CSS,使其以 position: absolute 的方式从顶部滑入,覆盖在主图之上。
    • 效果:
      • 图片加载时不再引起布局偏移。
      • 促销横幅的出现不再推移页面内容。
      • 最终 CLS 值降低到 ~0,完美解决了问题。

49-improving-interaction-to-next-paint

  • 改进下次绘制交互 (Interaction to Next Paint, INP)

    • 回顾 INP: 测量从用户交互(如点击)到下一次屏幕更新所需的时间,反映网站的响应速度。
  • 建立 INP 基线

    • 测量方法: INP 无法通过简单的页面加载来测量。需要在 Chrome 性能面板中 手动录制 一段包含交互(如点击“添加到购物车”按钮)的操作。
    • 当前基线: 点击按钮后,INP 高达 1200 毫秒 (1.2 秒),非常糟糕。
    • 原因分析: 点击事件触发了一个非常耗时的同步 JavaScript 任务(模拟的 updateAnalytics 函数)。
  • INP 的核心优化策略:让出主线程 (Yielding to the Main Thread)

    • 问题: 长时间运行的同步 JavaScript 会 阻塞主线程,导致浏览器无法处理其他任务,包括更新 UI(绘制下一帧)。
    • 解决方案: 将耗时的任务分解,或将其推迟到稍后执行,以便浏览器可以先快速地响应用户并更新界面。
  • 让出主线程的具体技术

    1. setTimeout(..., 0):
      • 这是最经典的“让出”技术。它将一个函数放入任务队列的末尾,在当前同步代码执行完毕、浏览器有机会处理其他任务(如 UI 更新)之后再执行。
    2. requestAnimationFrame():
      • 专门用于 UI 更新的 API。它请求浏览器在 下一次重绘之前 调用一个指定的回调函数。
      • 这是处理需要在用户交互后立即进行 UI 更新的理想时机。
  • 优化代码示例分析 (伪代码)

    // 原始的阻塞代码
    button.addEventListener("click", () => {
      doExpensiveAnalytics(); // 阻塞主线程
      updateUI();
      addToCartAPI();
    });
    
    // 优化后的非阻塞代码
    button.addEventListener("click", (event) => {
      // 1. 立即请求在下一帧更新UI
      requestAnimationFrame(() => {
        // 在这一帧,只做最轻量的UI更新
        button.textContent = "Added";
        button.disabled = true;
    
        // 2. 将耗时任务推迟到UI更新之后
        setTimeout(() => {
          doExpensiveAnalytics(); // 现在执行不会阻塞初次UI响应
        }, 0);
    
        // 3. 异步API调用本身就是非阻塞的
        addToCartAPI();
      });
    });
    
  • 在接下来的部分,将实际应用这些技术来重构点击事件的处理函数。

50-yielding-the-main-thread

  • 实际操作:重构“添加到购物车”的点击事件

    • 原始代码:

      document.addEventListener("click", async (e) => {
        if (e.target.matches(".add-to-cart")) {
          const productId = e.target.dataset.productId;
          updateAnalytics(productId); // 耗时操作,阻塞!
          await addToCart(productId);
          // ... 更新购物车 ...
        }
      });
      
    • 重构后的代码:

      document.addEventListener("click", (e) => {
        if (e.target.matches(".add-to-cart")) {
          const button = e.target;
          const productId = button.dataset.productId;
      
          // 1. 请求在下一帧执行UI更新
          requestAnimationFrame(async () => {
            // ----- P in INP happens here! 浏览器可以绘制了 -----
            // a. 立即给用户反馈
            button.textContent = "Added";
            button.disabled = true;
      
            // b. 将耗时的分析任务推迟到下一轮事件循环
            setTimeout(() => {
              updateAnalytics(productId); // 昂贵任务,但不阻塞初始响应
            }, 0);
      
            // c. 执行异步API调用
            await addToCart(productId);
      
            // d. 在一段时间后恢复按钮状态
            setTimeout(() => {
              button.textContent = "Add to Cart";
              button.disabled = false;
            }, 1500);
          });
        }
      });
      
  • 优化效果分析(火焰图)

    • 优化前: 点击事件触发了一个长长的任务条,包含了昂贵的 updateAnalytics,导致从交互到下一次绘制的时间非常长。
    • 优化后:
      1. 点击事件触发的第一个任务 极其短暂。它只做了一件事:调用 requestAnimationFrame。然后主线程就空闲了。
      2. 浏览器随后准备绘制下一帧,并触发了 requestAnimationFrame 的回调。
      3. 这个回调任务也很短,它快速地修改了按钮的文本和状态,然后通过 setTimeoutupdateAnalytics 安排到未来执行。
      4. INP 的测量到此结束。从用户点击到按钮状态改变(下一次绘制),时间极短。
      5. 之后,updateAnalytics 任务作为一个独立的长任务在后台执行,但它已经不再影响用户的感知响应速度了。
  • 最终效果

    • 点击按钮后,UI 立即响应(按钮文本变为"Added"),感觉非常流畅。
    • 使用性能面板分析,INP 时间从 1200ms 降低到了亚毫秒级别 (sub-millisecond),性能问题得到彻底解决。

51-wrapping-up

  • 课程回顾与核心要点总结
  • 为什么 Web 性能重要?
    1. 用户体验: 满足用户期望,减少因等待而产生的挫败感。
    2. SEO: 核心 Web 指标是 Google 页面排名的一个重要因素。
    3. 广告回报率: 快速的网站能降低跳出率,提高广告投入的转化。
  • 关键用户期望数据
    • 3 秒定律: 40%的用户会放弃加载超过 3 秒的网站。
    • 回头客: 75%的用户不会再访问他们认为慢的网站。
  • 核心 Web 指标 (Core Web Vitals)
    • LCP (最大内容绘制): < 2.5 秒。
    • CLS (累积布局偏移): < 0.1。
    • INP (下次绘制交互): < 200 毫秒。
  • 数据类型的重要性
    • 实验室数据 (Lab Data) (Lighthouse, WebPageTest): 用于 诊断 和调试。
    • 现场数据 (Field Data) / RUM (CrUX, 自建监控): 反映 真实用户体验,是性能的最终标准。
  • 性能优化的黄金法则
    • 将性能指标与 业务指标 关联起来,证明性能优化的商业价值。
    • 了解你的用户: 根据你自己的用户数据(设备、网络、地理位置)来指导测试和优化。
    • 优化策略: 基于真实用户数据,首先解决最差的指标,并从最简单的修复方案开始。
  • 后续行动建议
    1. 立即测试你的项目: 使用 Speed Check 等工具快速了解你的网站在 CrUX 中的表现。
    2. 部署 RUM 工具: 如果条件允许,安装一个 RUM 工具(如 Request Metrics)来获取更详细、更实时的用户数据。
    3. 继续学习: 关注进阶的 Web 性能课程,深入了解更高级的优化技术。
    4. 分享你的成果: 在社交媒体上分享你的学习心得和性能优化成果。