周末晚上的“老王家小馆”座无虚席,后厨只有三个灶眼,十桌客人在催菜。如果你是老板会怎么办?老师傅给出三招:第一,别把菜单上一百道菜全提前烧好——顾客点什么才下锅,这叫现点现做;第二,根据老客的翻牌习惯,把点击率最高的几道菜的食材提前备好——这叫备菜预判;第三,把土豆压成土豆泥粉、高汤浓缩成汤块,杂物间瞬间空出一半——这叫真空包装。把这三招装进浏览器,正是前端性能优化的三大绝技:懒加载、预加载、资源压缩。如果再配一台“顾客满意度仪表盘”(Core Web Vitals),外加一位会用数据预测点餐的 AI 店长,你的网站就能像五星餐厅一样——上菜快、不浪费、回头客多。本文就用这家小馆的后厨故事,带你吃透前端性能优化的全部门道。

岗位关联

理解并落地懒加载、预加载与资源压缩,是前端工程师晋升高级岗位的必修课——它直接决定页面加载速度、用户体验评分与业务转化率,是你在 AI 时代与智能工具协作优化网页时的底层沟通语言。

目录
  1. 可视化演示
  2. 性能优化概览
  3. 图片懒加载
  4. 资源预加载
  5. 资源压缩
  6. 关键渲染路径优化
  7. 性能指标评估
  8. AI赋能优化
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示懒加载、预加载与资源压缩三大前端性能优化技术:Intersection Observer按需加载图片、preload/prefetch资源优先级调度、Gzip/Brotli压缩率对比与Core Web Vitals指标。

本节配有交互动效(进入视口后自动加载,约 3KB)。

懒加载可视化 Intersection Observer监测图片进入视口 仅加载可见区域资源,减少初始请求 load = isVisible ? fetch(src) : skip 懒加载可减少50%+初始加载量

性能优化概览

老王家小馆开业一个月,老板发现一个扎心规律:翻台率最高的不是菜最好吃的时段,而是上菜最快的那几天。客人等上十分钟还见不到菜,起身就走,连差评都懒得留。这就是前端世界的铁律——速度本身就是用户体验的一部分。页面加载超过三秒,半数用户会放弃;再多一秒,转化率就掉一截。优化性能,本质上是在优化用户的“等待体验”。

把网站想象成那家后厨:浏览器就是小小的灶台,网络带宽是食材运输通道,HTML/CSS/JS/图片就是一份份食材。我们要做的,不是让后厨更忙,而是让有效内容更快上桌。所谓有效内容,就是用户当前真正看到、真正需要的那部分页面——首屏文字、关键图片、核心功能。围绕这个目标,厨房里诞生了三大招数:懒加载(现点现做)、预加载(备菜预判)、资源压缩(真空包装),再加上一套指标系统来衡量上菜速度是否达标。

性能优化的收益可以凝练成一个比值:用户感知到的价值除以等待时间。等待时间越小,感知价值越大,体验评分越高。

回到小馆:老板给每桌发了一个“上菜计时器”,把等待时间量化。前端也一样,我们有 Core Web Vitals 这套官方“计时器”。记住,性能优化的第一课不是写代码,而是先建立“时间即体验”的直觉。

性能感知=有效内容价值用户等待时间\displaystyle \text{性能感知} = \frac{\text{有效内容价值}}{\text{用户等待时间}}
这个公式说明:优化不是玄学,要么提高分子(更快呈现有价值内容),要么缩小分母(减少用户等待),两条路都能通往好体验。
  1. 有效内容价值用户当前最需要的页面信息,比如首屏标题、主图、购买按钮。它们价值最高,要先保障。
    有效内容价值\text{有效内容价值}
  2. 用户等待时间从点击链接到页面完全可用的总耗时,包含网络请求、资源加载、浏览器渲染等环节。
    用户等待时间\text{用户等待时间}
  3. 比值关系分子大、分母小,性能感知就高。所有优化手段,最终都是围绕这个分数做文章。
    有效内容价值用户等待时间\frac{\text{有效内容价值}}{\text{用户等待时间}}

图片懒加载

王老板发现,后厨最浪费的环节是“备餐台”——备餐员把一百道菜的半成品全摆上台,每道菜都占地方,可大部分直到打烊都没人点。他决定改规矩:顾客点什么,备餐员才从冷库里取什么。这就是懒加载的核心思想:按需取用,不做无用功。

网页里的图片就是那一盘盘半成品。一个长电商页面可能有上百张商品图,用户首屏只看得见五六张,其余的要滚动好几屏才出现。如果页面一打开就把一百张图全部下载,网络通道瞬间被堵死,首屏反而出不来。懒加载的做法是:图片进入视口(浏览器可视区域)附近时才开始下载,没出现在视口里的图片先按兵不动。

落地懒加载目前有两大法门。一是浏览器原生属性 loading="lazy",给 <img> 标签加上它,浏览器自动判断;二是更精细的 JavaScript 方案——Intersection Observer,它是浏览器提供的“视口哨兵”,专门监听元素与视口的交叉状态,当图片出现在预设范围时触发加载回调。判定逻辑可以写成:

其中 $\rho$ 是图片与视口的相交比例,$\theta$ 是预设阈值。

懒加载省下的流量相当可观。假设一百张图总重 20MB,首屏只用了 2MB,剩余 90% 的流量按需分配。回到小馆,这就是“备餐台终于清爽了,客人翻台率反而上去了”。要注意的是,首屏关键图片(比如主视觉横幅)必须立即加载,把它们也懒加载就本末倒置了——那相当于把招牌菜也藏进冷库,客人来了还得等解冻。

load={1,ρ>θ0,其他\displaystyle \text{load} = \begin{cases} 1, & \rho > \theta \\ 0, & \text{其他} \end{cases}
相交比例超过阈值才加载,相当于后厨规定:“客人坐定并看到这道菜超过两秒”,备餐员才开始做。
  1. 相交比例 ρ图片可见部分占其总面积的比例,取值范围 0 到 1。滚动后图片露出一半,ρ 就是 0.5。
    ρ\rho
  2. 阈值 θ预先设定的加载触发线,比如 0.1,表示图片露出 10% 就该加载了。
    θ\theta
  3. 条件判定当 ρ > θ 时,加载标记为 1(触发加载);否则为 0(继续等待)。
    ρ>θ\rho > \theta

资源预加载

王老板后来发现,光是“现点现做”还不够快。老主顾老张每周五都点红烧肉,服务员还没开口,后厨已经把肉下锅了。这就是备菜预判:根据经验预测“下一步可能要什么”,提前做好准备。懒加载解决的是“不浪费”,预加载解决的则是“再快一步”。

前端有两个容易混淆的预加载指令:preload 和 prefetch。preload 是“现在就要用到,请优先加载”——比如当前页面必需的字体文件、首屏大图,浏览器会以最高优先级去取;prefetch 是“将来可能用到,有空再取”——比如用户可能点击的下一个页面的资源、滚动到下方才会出现的模块,浏览器会在网络空闲时悄悄拉取。用后厨的话说,preload 是“这桌客人催得急,先上”,prefetch 是“看着像要加菜的客人,先备着”。

判断用哪种,本质是一个预测问题:评估用户下一步行为与所需资源的关联概率:

预加载的度要拿捏好。小馆复盘发现,提前备了 20 道菜,结果当天只卖出去 5 道,剩余食材全浪费了。前端的带宽也是“食材”:preload 滥用会把关键资源的优先级挤下去,prefetch 过量则白白消耗用户流量。正确的姿势是,用数据说话——分析用户行为路径,把预加载用在“翻牌率最高”的那几道菜上。

P(所需资源)=启发式预测(用户行为)\displaystyle P(\text{所需资源}) = \text{启发式预测}(\text{用户行为})
用户鼠标悬停在“下一页”按钮上、滚动接近页面底部、历史数据表明某模块高点击率——这些信号都会提高“未来资源”的预测概率。
  1. 用户行为信号点击、悬停、滚动静止等操作,都是预测下一步动作的线索。
    用户行为\text{用户行为}
  2. 启发式预测根据规则或历史规律推断“这个动作大概率走到哪里”,比如首页 → 列表页 → 详情页的典型路径。
    启发式预测\text{启发式预测}
  3. 概率 P预测值越高,越值得提前预取。但预取要克制,备了没人点的菜就是浪费带宽。
    P(所需资源)P(\text{所需资源})

资源压缩

王老板的杂物间终于堆满了。他请来一位老师傅,教他用真空封口机处理食材:土豆压成粉、高汤浓缩成块、干货抽真空。同样容积的储物柜,现在能装下三倍的食材。压缩不改变营养,只改变体积——这是资源压缩的哲学。

网页里的 HTML、CSS、JavaScript 都是纯文本,文本中充满大量重复的标签、空格、重复出现的单词。Gzip 就像那台真空封口机:在服务器端把文件压缩成更小的包,浏览器收到后再解压还原。Gzip 基于 DEFLATE 算法,核心思路是用更短的编码替代重复片段——比如“background-color“在 CSS 里出现 80 次,压缩器只记录一次,其余用指针引用。新一代的 Brotli 压缩算法做得更极致,它内置了一个巨大的常用词汇字典,连”class”"div"这类高频词都能直接查表,压缩率通常比 Gzip 再高 15%~20%。

压缩收益用“压缩率”来衡量:

但压缩也不是越高越好——压缩级别像真空机的抽气档位,档位越高压得越扁,但机器也越吃力。服务器端 CPU 会在高压缩级别下飙升,反而拖慢响应。合理的做法是:文本资源按内容类型区分,HTML/CSS/JS 开启 Gzip 或 Brotli,压缩级别设在 5~6;而 JPEG、PNG、视频这类本身已经压缩过的格式,再上真空机只会白费力气——它们就像已经被压成砖的冻干蔬菜,没有多少水分可挤了。

r=S原始−S压缩S原始×100%\displaystyle r = \frac{S_{\text{原始}} - S_{\text{压缩}}}{S_{\text{原始}}} \times 100\%
压缩率表示省掉了多少体积。一个 120KB 的 JS 文件经过 Brotli 压缩到 36KB,压缩率就是 70%,用户下载的数据量只有原来的 30%。
  1. 原始大小服务器上未压缩的文本资源体积,单位通常为字节。
    S原始S_{\text{原始}}
  2. 压缩后大小经过 Gzip 或 Brotli 处理后的体积。
    S压缩S_{\text{压缩}}
  3. 体积差与比值原始与压缩的差值代表节省的字节数,除以原始体积再乘 100%,得到直观的压缩率。
    S原始−S压缩S原始×100%\frac{S_{\text{原始}} - S_{\text{压缩}}}{S_{\text{原始}}} \times 100\%

关键渲染路径优化

小馆后厨的动线曾是一团乱麻:食材从后门进,先穿过洗碗区,再绕过杂物间才到灶台,传菜又要走回前厅正中央。王老板把灶台移到传菜口旁边,食材进门直送备菜台,出菜路径缩短了一半。上菜速度不只看菜做得快不快,更看路径顺不顺——这就是关键渲染路径优化。

浏览器从拿到 HTML 到在屏幕上画出像素,也有一条固定动线,称为关键渲染路径。它包含五步:解析 HTML 构建 DOM 树、解析 CSS 构建 CSSOM 树、两者合并成 Render Tree(渲染树)、计算布局(Layout)、最后绘制合成。路径上任何一步被阻塞,屏幕上就多一片空白。CSS 和 JavaScript 是这条路上最常见的“堵点”:CSS 在构建 CSSOM 时会阻塞渲染;普通 <script> 标签在下载和执行时会阻塞 DOM 解析,就像有人在厨房过道里停下来打电话。

关键路径的总耗时可近似看成各环节耗时之和:

对应小馆动线调整,前端的关键渲染路径优化有三板斧:把关键 CSS 直接内联进 HTML 头部,省去“从仓库取样式”的往返;给非关键的 JavaScript 加 defer 或 async,让它们别堵在 DOM 解析的路上;尽量减少请求数量和资源体积,让每件“食材”进门都不用绕路。路径顺了,首屏自然哗地一下亮出来。

t关键=tHTML+tCSS+tJS+t绘制\displaystyle t_{\text{关键}} = t_{\text{HTML}} + t_{\text{CSS}} + t_{\text{JS}} + t_{\text{绘制}}
每一项数值变小,总时间就变小。优化的本质是:让这条路径上的每一步都尽量轻、尽量快、尽量不相互等待。
  1. HTML 解析耗时浏览器读取 HTML 代码、构建 DOM 树的时间,精简标签和注释能略微加速。
    tHTMLt_{\text{HTML}}
  2. CSS 构建耗时解析样式表、构建 CSSOM 树的时间。关键 CSS 内联进 HTML 可减少一次往返请求。
    tCSSt_{\text{CSS}}
  3. JS 执行耗时脚本下载和执行的时间。defer 让脚本在 DOM 解析完成后执行,async 让独立脚本并行下载后立即执行,都避免阻塞。
    tJSt_{\text{JS}}
  4. 绘制合成耗时布局计算与像素绘制的总时间。减少 DOM 层级、避免大型图片重绘,都能降低此值。
    t绘制t_{\text{绘制}}

性能指标评估

王老板做了个“顾客满意度仪表盘”:多久上菜?喊服务员多久有人应?上菜时桌子有没有晃? 三个问题对应三个可量化的数字,每周复盘,持续改进。前端领域也有一套官方仪表盘,叫 Core Web Vitals,由三项核心指标组成。

第一项是 LCP(Largest Contentful Paint,最大内容绘制),衡量“最大的内容块”(通常是首屏主图或大标题)多久显示出来,好比“主菜多久上桌”,达标线是 2.5 秒以内。第二项是 INP(Interaction to Next Paint,交互到下一帧绘制),衡量用户点击或按键后页面多久给出视觉反馈,好比“喊服务员多久有人应”,达标线是 200 毫秒以内。第三项是 CLS(Cumulative Layout Shift,累积布局偏移),衡量页面元素是否突然跳动,好比“上菜时桌子有没有晃倒杯子”,得分应小于 0.1。三项指标综合起来,构成一个整体体验分:

其中 $\hat{\text{LCP}}$、$\hat{\text{INP}}$、$\hat{\text{CLS}}$ 分别是三项指标按阈值归一化后的 0~1 分。

小馆的仪表盘周报会显示“上菜平均 4 分钟、应声 30 秒、翻桌 0 次”,老板据此调整备菜策略。前端工程师也该养成同样的习惯:每次改动后跑一遍指标,看看懒加载有没有改善 LCP、压缩有没有缩短传输时间、布局有没有引入新的 CLS。没有指标的优化是盲人摸象,有了指标,每一次改动都有据可依。

Vitals=13(LCP^+INP^+CLS^)\displaystyle \text{Vital}_s = \frac{1}{3}\left(\hat{\text{LCP}} + \hat{\text{INP}} + \hat{\text{CLS}}\right)
三项平均分越接近 1,体验越好。每一项不达标,都会拖累整体评分,就像三桌客人同时差评,口碑分一定崩。
  1. LCP 评分映射将实际测量的 LCP 时间(如 3.2 秒)映射为 0~1 的评分,超时越多分越低。
    LCP^\hat{\text{LCP}}
  2. INP 评分映射将交互响应延迟映射为评分,越流畅越接近 1。
    INP^\hat{\text{INP}}
  3. CLS 评分映射将布局偏移量映射为评分,页面越稳定分越高。
    CLS^\hat{\text{CLS}}
  4. 取平均三项相加除以 3,得到综合体验分。实际操作中常用工具(如 PageSpeed Insights、Lighthouse)自动计算并给出诊断报告。
    13(LCP^+INP^+CLS^)\frac{1}{3}\left(\hat{\text{LCP}} + \hat{\text{INP}} + \hat{\text{CLS}}\right)

AI赋能优化

老王的生意越做越大,仅靠老师傅的经验已经压不准“明天该备什么菜”。他请来一位数据店长,把三个月的外卖订单、天气、节假日、顾客口味全喂给一套模型。模型学会了预测:雨天烧烤类订单上涨 40%、周五下午甜点翻倍。后厨从此按预测备菜,浪费少了,高峰期也不缺货了。当经验判断升级为数据预测,优化就从“人治”进化到“智治”。

前端性能优化正在经历同样的进化。传统的预取靠开发者的经验写死规则,而 AI 赋能的预测预取会收集海量用户行为序列——鼠标轨迹、停留时长、点击习惯——训练一个模型,实时预测“当前用户大概率下一步访问哪个页面、需要哪些资源”,然后自动下发预取指令。预测越准,缓存命中率越高,用户感知到的加载时间就越短。训练这类模型的常用目标函数是带正则化的交叉熵损失:

其中 $y_i$ 是用户真实行为的 one-hot 标签,$\hat{y}_i$ 是模型预测概率,$\lambda$ 控制正则强度。

除了预测预取,AI 还渗透到智能压缩(根据终端网络状况自动选择 Gzip/Brotli 及压缩级别,4G 用户拿到更小的包,WiFi 用户享受更快的解压)和异常监控(自动识别 Core Web Vitals 的异常波动并定位原因)。小馆的数据店长最终让翻台率提升了三成——AI 不是取代后厨,而是让每个决策都建立在数据而非拍脑袋上。对前端工程师而言,理解 AI 优化的底层逻辑,意味着你能更好地与算法团队协作,把“经验判断”升级成“可学习的系统”。

L(w)=−∑iyilog⁡y^i+λ∥w∥2\displaystyle \mathcal{L}(w) = -\sum_{i} y_i \log \hat{y}_i + \lambda \|w\|^2
交叉熵度量“预测与现实的差距”,正则项约束模型别盲目记住历史数据。AI 店长学的不是某一天的具体订单,而是“晴天周末下午该多备哪些菜”这类稳定规律。
  1. 真实标签 $y_i$用户实际点击或访问的资源,用 one-hot 编码表示——某个时刻真正发生了哪件事。
    yiy_i
  2. 预测概率 $\hat{y}_i$模型估算的用户访问该资源的概率,像一个不断校准的“备菜权重表”。
    y^i\hat{y}_i
  3. 交叉熵项逐样本累计预测偏差,偏差越大损失越大,训练就是不断把这个值压小。
    −∑iyilog⁡y^i-\sum_{i} y_i \log \hat{y}_i
  4. 正则项对模型参数 $w$ 施加惩罚,防止它把偶然的噪声当成规律,保证预测在真实场景中依然稳健。
    λ∥w∥2\lambda \|w\|^2

AI 应用场景

电商瀑布流页面的智能懒加载

阶段:数据处理与前端渲染优化

解决的问题:传统电商首页包含数百张商品图,一次性全量加载会导致首屏白屏数秒、流量消耗巨大。AI 结合用户浏览行为与推荐系统,动态决定图片的加载优先级:推荐算法判定“你可能想看的商品”被赋予高优先级,提前进入视口开始加载;其余商品图则交给 Intersection Observer 按滚动位置懒加载,既保首屏速度,又不浪费带宽。

具体案例:某头部电商 App 大促期间,首页采用“AI 排序 + 懒加载”组合策略,商品图按用户偏好预加载前 10 张,其余随滚动懒加载。实测 LCP 从 4.2 秒降至 1.8 秒,流量消耗减少 55%,人均浏览商品数提升 20%。

视频平台的下一集预加载

阶段:用户行为预测与资源预取

解决的问题:用户看完剧集后,传统做法是等用户点击“下一集”才开始缓冲,等待期长达数秒。AI 模型分析用户观看时长、完播率与历史行为,对“80% 概率会看下一集”的用户提前下发 prefetch 指令,下一集视频的首个分片在后台悄悄就绪,点击瞬间即播。

具体案例:视频网站对高留存用户启用 AI 预加载后,连续播放会话的平均续播等待从 3.5 秒降至 0.4 秒,用户次日留存率提升 8 个百分点,同时通过精确的置信度阈值控制,避免为“只看一集就离开”的用户浪费流量。

新闻资讯站的智能文本压缩

阶段:模型推理与网络传输优化

解决的问题:新闻门户以 HTML/JSON 文本为主,传统固定启用 Gzip 无法适配多样化终端。AI 系统根据用户设备类型、网络带宽和请求内容的冗余度,动态选择 Brotli 或 Gzip、动态设定压缩级别:弱网用户获得最高压缩比的 Brotli 资源,强网用户则用低级别压缩减少服务器 CPU 开销。

具体案例:某新闻平台接入自适应压缩系统后,移动端页面平均传输体积下降 62%,弱网下正文加载时间从 6.8 秒缩短到 2.9 秒,服务器 CPU 峰值反而降低 15%,因为不再对所有请求都做“最高档真空包装”。

Python 演示

python60 行
import zlib class LazyLoader:    """模拟浏览器视口懒加载调度"""     def __init__(self, viewport_height=800):        self.content = []   # [(元素距顶距离, 资源体积)]        self.loaded = set() # 已加载元素下标        self.scroll_y = 0        self.viewport_height = viewport_height     def add_item(self, position, size):        self.content.append((position, size))     def scroll(self, delta):        self.scroll_y += delta     def update(self):        """返回新加载资源数, 模拟 IntersectionObserver 回调"""        newly = 0        for i, (pos, size) in enumerate(self.content):            if pos < self.scroll_y + self.viewport_height and i not in self.loaded:                self.loaded.add(i)                newly += 1        return newly     def transferred(self):        return sum(size for i, (pos, size) in enumerate(self.content) if i in self.loaded)  def gzip_report(data):    compressed = zlib.compress(data, level=6)    return len(data), len(compressed), 1 - len(compressed) / len(data)  def main():    # 长页面: 50 张图片, 每张 200KB, 每张间距 400px    loader = LazyLoader()    for i in range(50):        loader.add_item(i * 400, 200 * 1024)     # 用户向下滚动 30 次, 每次 200px    while loader.scroll_y < 30 * 200:        loader.scroll(200)        loader.update()     eager = sum(size for _, size in loader.content)    lazy_total = loader.transferred()    print(f"滚动 30 次后, 懒加载传输 {lazy_total / 1024:.0f} KB")    print(f"非懒加载全量传输   {eager / 1024:.0f} KB")    print(f"节省流量 {(1 - lazy_total / eager) * 100:.1f}%")     # Gzip 压缩文本演示    html = b"<html><body>" + b"".join(b"<p>hello frontend performance</p>" for _ in range(10000)) + b"</body></html>"    raw, comp, ratio = gzip_report(html)    print(f"HTML 原始大小 {raw} B, Gzip 后 {comp} B, 压缩率 {ratio * 100:.1f}%")  if __name__ == "__main__":    main()
目的:用纯 Python 模拟两类性能优化——懒加载按视口调度资源,以及 Gzip 对文本的压缩效果,让你直观看到“少传多少、压扁多少”。
  1. def update(self):模拟 IntersectionObserver 的回调:每次滚动后检查哪些图片落入视口,首次进入的图片加入已加载集合。浏览器里这项工作由 Intersection Observer 在后台异步完成。
  2. pos < self.scroll_y + self.viewport_height判定“元素是否可见”的核心条件:元素距页顶的距离小于(当前滚动高度 + 视口高度),说明它已经出现在用户眼前。
  3. self.loaded.add(i)用集合记录已加载下标,保证同一图片只加载一次,避免重复请求——对应面试题中“反复进出视口不会重复加载”的答案。
  4. lazy_total / 1024:.0f把字节数换算成 KB 并保留整数,方便对比懒加载与全量加载的传输差距。
  5. zlib.compress(data, level=6)Python 标准库的 DEFLATE 压缩,等价于服务器开启 Gzip 后对响应体做的事;第六级是“性价比”较高的档位。
  6. 1 - len(compressed) / len(data)计算压缩率,与正文公式 r = (S原始 - S压缩) / S原始 完全一致。

常见误区

误区我只要给所有图片都加上 `loading="lazy"`,或者用 Intersection Observer 拦截所有资源,性能就一定会变好。首屏主图也可以懒加载,反正最后都会加载。

正解懒加载只应该作用于当前视口以外、用户未来才可能看到的资源。首屏主视觉、关键标题、核心功能脚本必须立即可用——把它们懒加载等于把招牌菜藏进冷库,客人一坐下就要等“解冻”,首屏体验反而崩坏。后厨的“现点现做”也分对象:招牌菜要提前备好半成品,只有配菜才等客人点了再下锅。

为何常见:初学者只看到了“延迟加载省流量”这一个收益,忽略了“用户第一眼看到的内容优先级最高”这一基本原则。

误区preload 和 prefetch 都是“提前加载资源”,用哪个都行,多加几条总没坏处。

正解两者定位截然相反。preload 是当前页面必须的资源,优先级最高,浏览器会暂停其他任务来抢它;prefetch 是未来可能用的资源,浏览器只在空闲时低优先级去取。把当前页面关键字体用 prefetch 声明,可能导致首屏字体晚到;把未来页面的资源用 preload 强行提前,则会挤占关键资源带宽。这就像后厨的“催菜铃”和“备菜清单”不能互换——催菜铃响了必须立刻上菜,备菜清单只是有空时准备。

为何常见:两者名字都有“pre“前缀,功能都是”提前”,初学者很难立刻分辨使用场景的差异。

误区Brotli 比 Gzip 好,那我就把所有资源都用最高压缩级别处理;图片、视频也再压一遍,能省一点是一点。

正解压缩级别越高,服务器 CPU 消耗越大,响应时间可能变长,反而拖慢整体体验。更关键的是,JPEG/PNG/WebP 这类已压缩格式内部几乎没有冗余可挤,再做 Gzip/Brotli 收益极小甚至体积反向变大。正确做法是:只对 HTML/CSS/JS/JSON 等文本资源开启压缩,级别取 5~6 的性价比档位。真空机也有适用范围——把冻干蔬菜再抽一遍真空,只是浪费电。

为何常见:看到“压缩能减小体积 70%”的案例后,容易把结论外推到所有文件类型,忽略了压缩算法只对高冗余文本有效。

误区我把懒加载、预加载、Gzip 都配好了,性能指标也看过一遍是绿色的,之后就不用再管了。

正解性能是一个持续变化的动态指标:业务新增图片、第三方脚本升级、用户设备换代、网络环境变化,都会让 Core Web Vitals 从绿变红。正确做法是把性能监控纳入日常开发流程,每次发布后跑一遍 PageSpeed Insights,每周查看真实用户数据(RUM)。小馆的满意度仪表盘也是每周复盘,从不敢“调一次就永远不管”。

为何常见:优化是“一次性项目”的惯性思维——觉得配好配置就算完成任务,忘了软件系统永远在演进。

误区图片用懒加载后,搜索引擎爬虫可能抓不到图片,导致 SEO 排名下降,所以企业站不敢用懒加载。

正解搜索引擎(Google 等)的爬虫支持标准懒加载实现,包括 loading="lazy" 和基于 Intersection Observer 的常见方案;规范要求为图片提供 src 兜底或结构化数据,确保爬虫能解析真实地址。只要不把图片路径藏在 JS 变量里且不提供任何兜底,懒加载不会伤害 SEO。这相当于后厨给外送平台报菜名时写清楚每道菜的真实食材,而不是说“等客人点了再说”。

为何常见:早期确实存在爬虫不执行 JS 导致图片漏抓的旧闻,加上对“延迟加载”字面的担忧,让这个过时顾虑流传至今。

面试题

Q1. 下列哪项最符合图片懒加载的核心原理?

单选题 · easy

  • A.把所有代码文件打包压缩成一个文件
  • B.等元素进入视口时才加载对应资源← 正确答案
  • C.将页面全部图片提前预取到本地缓存
  • D.利用 CSS 隐藏不需要展示的图片元素

答案:B。懒加载的关键是“按需加载”——资源进入视口(用户看得到)才发起请求,对应后厨“顾客点了才下锅”。A 是打包压缩,C 是预加载,D 是用 CSS 隐藏,三者都与懒加载的触发时机无关。

Q2. Intersection Observer 在前端性能优化中的作用是?

单选题 · mid

  • A.专门监听用户键盘输入事件的对象
  • B.高效检测元素与浏览器视口的交叉状态← 正确答案
  • C.用于修改页面全局字体样式的浏览器接口
  • D.发起网络请求获取远程 JSON 数据的工具

答案:B。Intersection Observer 是浏览器提供的“视口哨兵”,异步监听元素是否进入可视区域,性能远优于传统的 scroll 事件监听。它不负责输入事件、样式修改或网络请求,专司“看见没看见”这一件事。

Q3. preload 与 prefetch 的主要区别是什么?

单选题 · mid

  • A.preload 等待页面加载完毕后才开始执行
  • B.prefetch 的下载优先级通常高于 preload
  • C.preload 服务于当前页面必需资源,prefetch 备战未来资源← 正确答案
  • D.两个指令描述的是完全相同的加载行为

答案:C。preload 告诉浏览器“这个资源本页马上要,请立即高优加载”;prefetch 则说“未来某个页面可能用,空闲时再取”。优先级上 preload 高于 prefetch。这就好比后厨的“催菜铃”和“备菜清单”,用途完全不同。

Q4. 以下哪些做法有助于减少关键渲染路径上的阻塞?

多选题 · mid

  • A.将关键 CSS 内联到 HTML 文件头部← 正确答案
  • B.给 script 标签添加 defer 属性延迟执行← 正确答案
  • C.把 CSS 放 body 末尾延迟样式解析
  • D.用大图作为页面背景增强视觉冲击

答案:AB。关键渲染路径要求 CSS 尽快构建 CSSOM,所以关键 CSS 宜内联、不宜放 body 末尾(C 错);defer 让脚本在 DOM 解析完成后执行,避免阻塞(B 对);大图背景会显著拖慢 LCP 并增加布局负担(D 错)。后厨动线讲究“食材进门直送灶台”,A、B 正是缩短动线的做法。

Q5. 关于 Gzip 与 Brotli 压缩算法,下列判断正确的是?

单选题 · easy

  • A.二者是互斥的同一种压缩算法
  • B.Brotli 的压缩率通常优于 Gzip← 正确答案
  • C.Gzip 的压缩率恒高于 Brotli
  • D.两者都无法有效压缩文本类资源

答案:B。Gzip 基于 DEFLATE,Brotli 是新一代算法,内置常用词汇字典,对 HTML/CSS/JS 这类文本通常比 Gzip 再小 15%~20%。二者各有适用场景,绝非互斥或恒定优劣,也都擅长压缩文本资源。

Q6. 关于 Core Web Vitals 三项指标,下列说法正确的是?

多选题 · hard

  • A.LCP 度量最大内容块的绘制时间← 正确答案
  • B.INP 度量交互到下一帧的响应延迟← 正确答案
  • C.CLS 度量页面元素的累积布局偏移← 正确答案
  • D.移动端用户无需关注这三项指标

答案:ABC。LCP(最大内容绘制)衡量首屏主菜多久上桌,INP(交互到下一帧绘制)衡量喊服务员多久有回应,CLS(累积布局偏移)衡量上菜时桌子稳不稳。三项指标在桌面端与移动端都同等重要,D 明显错误。

Q7. 某文本文件原始 200KB,经 Brotli 压缩后剩 40KB,其压缩率是?

单选题 · extreme

  • A.压缩率 20%, 表示压缩后是原始的 20%
  • B.压缩率 80%, 表示减少了 80% 的体积← 正确答案
  • C.压缩率 500%, 表示体积扩大了 5 倍
  • D.压缩率 40%, 表示压缩后占原始的四成

答案:B。压缩率 r = (S原始 − S压缩) / S原始 × 100% = (200 − 40) / 200 = 80%,表示节省了 80% 的体积,剩下载体仅 20%。A 把“剩余占比”当压缩率,C 搞反了方向,D 把数值安成了压缩后比例。

Q8. 懒加载场景下,某图片反复滚动进出视口,会发生什么?

单选题 · hard

  • A.浏览器会重复发起多份相同的图片请求
  • B.加载器会用已加载集合去重,只加载一次← 正确答案
  • C.页面会因循环触发条件而直接崩溃
  • D.图片将因为抖动判定而永远无法加载

答案:B。成熟的懒加载实现都会记录已加载资源的标识(如 loaded 集合),首次进入视口触发加载后即标记,后续反复进出只更新观察状态,不再重复请求。这就像后厨记录了“红烧肉已下锅”,客人反复催几次也不会做第二份。

Q9. 关于资源压缩的正确实践,以下哪些描述成立?

多选题 · mid

  • A.对 HTML 文本资源启用 Gzip 或 Brotli 压缩← 正确答案
  • B.对 JPEG 图片再一次做 Gzip 压缩以减小体积
  • C.压缩级别设置过高会显著增加服务器 CPU 负担← 正确答案
  • D.无损压缩算法会明显降低图片的视觉清晰度

答案:AC。文本资源冗余高、压缩收益大,A 对;JPEG 内部已是有损压缩格式,再压近乎无收益甚至变大,B 错;压缩级别越高 CPU 开销越大,可能拖慢服务器响应,C 对;Gzip/Brotli 属于无损压缩,解压后内容原样还原,不影响清晰度,D 错。

Q10. 图片懒加载带来的最直接收益是什么?

单选题 · easy

  • A.减少首屏请求数量与流量消耗← 正确答案
  • B.自动提升图片的画面清晰度
  • C.修复图片加载完成后的布局错乱
  • D.让服务器预先缓存所有图片文件

答案:A。懒加载把视口外的图片请求推迟,首屏只下载可见资源,请求数和流量同步下降,这正是“现点现做”省食材的效果。清晰度取决于图片源文件,布局错乱要靠占位尺寸解决,服务器缓存是另一套机制,均与懒加载无直接关系。