ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

预加载性能优化实战:从资源提示到缓存预热与度量

预加载性能优化实战:从资源提示到缓存预热与度量

预加载这个词,我在不同场合听到过太多版本:前端同学说的是<link rel="preload">,做游戏的朋友说的是资源包预热,后端同事聊的是缓存预热,做多媒体的同行讲的是视频缓冲。名字一样,骨子里的逻辑也差不多——在用户真正需要某个东西之前,先把它准备好。听着简单,真落地的时候坑不少:预加载了却没命中缓存、预加载抢了关键资源的带宽、预加载了一堆用户根本用不到的东西。这篇就把预加载这件事从头到尾捋一遍,从它到底在解决什么问题,到 Web 端各类资源提示的选型,再到图片、媒体、接口、服务端缓存的实操细节,最后是度量和排查。前端、全栈、客户端方向的同学都能对号入座,刚接触性能优化的新手看完也能直接上手抄配置。

1. 预加载到底在解决什么问题

1.1 从一次首屏卡顿说起

前阵子帮一个做内容站点的朋友看首屏,场景很典型:页面 HTML 返回很快,TTFB 不到 200ms,但用户在手机上看到完整首屏要三秒多。抓了一遍瀑布图,问题一目了然——首屏那张主图是在 CSS 解析完、DOM 树建好之后才被发现的,浏览器在这之前完全不知道有这么个资源要下。也就是说,从 HTML 到达浏览器到主图开始下载,中间白白空了一秒多。

这就是预加载要解决的第一类问题:资源发现的时机太晚。浏览器解析 HTML 是流式的,遇到<img>才知道要下载图片,遇到<script>才知道要拉脚本。如果关键资源藏在 CSS 的background-image里、藏在 JS 动态插入的标签里、藏在懒加载逻辑后面,那它的下载起点就被推得很靠后。预加载做的事情,本质上是把「发现资源」这个动作提前,让下载和解析并行起来。

第二类问题是连接的建立成本。跨域请求要经历 DNS 解析、TCP 握手、TLS 协商,在移动网络下这一套下来几百毫秒很正常。哪怕资源本身只有 10KB,等连接建好,用户已经感觉到卡了。所以除了预加载资源本身,还有一类技术专门预加载「连接」。

第三类问题更隐蔽:用户下一步要干什么,系统其实猜得到,但没提前准备。用户把鼠标移到导航链接上、手指开始横向滑动轮播、搜索结果页还在滚动,这些信号都预示下一个请求即将到来。预加载的高级玩法就是利用这些信号做预测性加载。这三类问题对应三种不同层次的手段,后面的章节会逐一展开。

1.2 预加载的本质:把等待挪到用户不感知的地方

我习惯用一个类比来解释预加载:去餐厅吃饭,厨师不可能等你点完单才开始洗菜切菜,备菜这件事一定是在营业前就做好的。预加载就是「备菜」,核心不是让总耗时变短,而是让用户能感知到的等待变短。

这里有个关键认知需要摆正:预加载几乎从来不减少总的网络传输量,很多时候还会增加——预加载了但用户没用上,这部分流量就是纯浪费。它优化的是时间分布,把耗时的动作挪到用户注意力不在的那个时间段里。所以判断一个预加载方案值不值,不能只看「加载快了没有」,得看「浪费了多少」和「抢占了多少」这两笔账。

浪费很好理解,预加载命中率低就是浪费。抢占则更容易被忽略:浏览器和服务端的带宽、连接数、CPU 都是有限的,你给一个非关键资源开了高优先级预加载,它就会和首屏最关键的那张图抢同一条 TCP 连接的带宽。我见过有项目把首屏所有图片全加上preload,结果 LCP 反而从 2.1s 涨到 2.8s,原因就是这个——所有资源都成了关键资源,等于没有关键资源。

所以预加载的设计原则我总结成三句话:只预加载高确定性的资源,只给真正卡住渲染的资源提优先级,一定要有度量手段验证收益。后面所有具体技术,都是在这三条原则下做取舍。

1.3 三类典型误用,先避坑再谈优化

在讲具体技术之前,先把最常见的三类误用摆出来,因为它们出现的频率高得离谱。

第一类是无差别预加载。某后台管理系统为了「提升体验」,在路由切换时把剩下所有页面的 JS chunk 全部prefetch了一遍,二十多个页面加起来十几兆。结果是首页刚加载完,网络就被这些低优先级请求占满了,用户点进下一个页面时反而更慢。正确的做法是根据用户行为路径做加权,只预热高概率的下一站。

第二类是预加载和实际请求对不上。这个坑极其隐蔽:<link rel="preload" href="/api/list">预加载了接口,但实际发请求时带了个?page=1&ts=1699999的时间戳参数,URL 不一致,浏览器缓存里那份预加载结果完全用不上,等于白下。跨域资源漏写crossorigin也是同类问题,预加载用的是无凭证模式,实际请求带凭证,缓存键不同,两次下载。

第三类是预加载了却没人用。控制台那句The resource was preloaded using link preload but not used within a few seconds就是在提醒你。常见于条件渲染的组件,代码里预加载了弹窗里的图表库,但用户压根没点开弹窗。这类警告不要当噪音忽略,每一条背后都是一次真实的带宽浪费。

2. Web 端资源提示的技术图谱与选型

2.1 四种资源提示的定位差异

Web 平台提供的「预」类能力,按介入深度从浅到深大致是这么一条线:dns-prefetch→preconnect→prefetch→preload。很多人把它们混着用,其实四者的成本、收益、适用场景差别很大。

dns-prefetch最轻,只提前做域名解析,把域名到 IP 的映射结果缓存起来。成本几乎可以忽略,收益在跨域场景下通常是几十毫秒。写法就是<link rel="dns-prefetch" href="//cdn.example-cdn.com">。它适合那种「可能会用到但不确定」的第三方域名,多写几个也不心疼。

preconnect更进一步,除了 DNS 还要完成 TCP 握手和 TLS 协商。收益明显更大,但成本也上来了——每个连接都要占用服务端和客户端的资源,浏览器对同时保持的预连接数量有限制。我的经验是控制在三到四个以内,优先给字体域名、核心 CDN 域名、核心接口域名。写法上记得加crossorigin,因为大部分跨域资源都需要 CORS 模式连接:

<link rel="preconnect" href="https://cdn.example-cdn.com" crossorigin> <link rel="dns-prefetch" href="https://cdn.example-cdn.com">

两个一起写是常见做法,dns-prefetch作为preconnect不被支持时的降级。这个组合我基本在所有跨域资源多的项目里都会加。

prefetch面向的是「下一个页面可能用到」的资源,优先级最低,浏览器只在空闲时下载,结果放进 HTTP 缓存。它不会阻塞当前页面,但会消耗流量。用户在弱网或者流量敏感的场景下,prefetch要慎用,最好配合navigator.connection.saveData做判断。

preload是这四者里最强硬的:它告诉浏览器「当前页面一定会用到这个资源,请立刻按指定优先级下载」。注意是下载,不是执行。脚本预加载完了不会运行,样式预加载完了不会应用,都只是躺在内存或缓存里等着被真正引用。正因为强硬,用错了代价也最大。

2.2 preload 的正确姿势与几个必踩的坑

preload的语法不复杂,但细节很多。最基本的形式是<link rel="preload" href="/static/main.js" as="script">。as属性不是可选的装饰,它决定了浏览器用什么样的优先级、什么样的请求头、放进哪个缓存池。写错as的直接后果是资源被下载两次。

常见as取值和对应场景:

as 取值适用资源注意事项
scriptJS 文件模块脚本建议改用 modulepreload
styleCSS 文件预加载后仍需 link 引用才会应用
image图片响应式图片需配合 imagesrcset
font字体必须加 crossorigin,否则重复下载
fetchfetch/XHR 请求的数据需要 CORS 模式时加 crossorigin
documentiframe 或页面用得较少
video / audio媒体文件大文件慎用

字体这一项我要单独说,因为它是踩坑率最高的。字体请求默认是 CORS 模式的,<link rel="preload" as="font">如果不加crossorigin,浏览器会用 no-cors 模式预加载一份,实际用字体时又用 cors 模式请求一份,两份缓存键不同,白下一遍。这个问题在控制台会有明确提示,但很多人不看控制台。

<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>

还有个细节:type属性建议写上。浏览器可以据此判断自己是否支持该格式,不支持就直接跳过预加载,避免下载一个用不上的文件。woff2 现在支持度已经很好了,但写上总没坏处。

2.3 响应式图片的预加载:imagesrcset 怎么用

普通的<link rel="preload" as="image">只认一个固定 URL,遇到srcset就抓瞎了——浏览器在渲染阶段才会根据视口宽度和设备像素比挑选合适的候选图,预加载阶段它不知道最终会选哪张。这个问题在很长一段时间里让响应式图片没法享受预加载的收益。

后来标准补上了imagesrcset和imagesizes两个属性,让预加载也能参与候选图选择:

<link rel="preload" as="image" imagesrcset="/img/hero-480.webp 480w, /img/hero-960.webp 960w, /img/hero-1440.webp 1440w" imagesizes="(max-width: 600px) 100vw, 50vw" fetchpriority="high">

这里的imagesizes必须和<img>标签上的sizes完全一致,否则浏览器预加载的那张图和最终渲染选的那张图对不上,等于没预加载。这种「两处配置必须同步」的地方最容易在迭代中失配,我一般会把这两个值抽成同一份配置,模板里同时渲染,避免手改漏改。

fetchpriority="high"这个属性值得单独提一句。它作用在<img>和<link rel="preload">上,直接告诉浏览器这个资源的优先级。首屏主图、LCP 元素相关的资源,加这个属性效果立竿见影,比单纯preload更直接。反过来,非关键资源可以标fetchpriority="low",主动给它让路。

2.4 modulepreload 与脚本预加载的取舍

现代项目基本都是 ES module 了,<link rel="modulepreload">是对应的专用提示。它和preload as="script"的区别在于:modulepreload不仅下载,还会解析模块、建立依赖图,把静态 import 的子模块一并预加载。对于有明显依赖链的入口文件,这个收益比普通preload大得多。

不过要提醒一句,modulepreload会把整条依赖链都拉下来,如果入口文件的依赖图很庞大,预加载的动作本身可能就很重。我的做法是只对首屏必需的入口做modulepreload,路由级的 chunk 交给路由库自己的 prefetch 机制,按用户行为触发,不要一上来全拉。

preload as="script"在模块化项目里还有一个副作用:如果同一个模块既被preload又被import,部分浏览器可能因为请求模式差异(比如preload不带type="module"语义)导致重复请求。所以模块脚本我一律用modulepreload,不再混用。

2.5 Speculation Rules:声明式的预测性加载

<link rel="prefetch">的老问题是它只知道「预取资源」,不知道「预渲染页面」。用户点链接进去,HTML 有了、JS 有了,但还是要走一遍完整的解析、布局、执行流程。Speculation Rules API 把这件事推进了一步,可以用一段 JSON 声明什么样的链接在什么条件下预取甚至预渲染:

<script type="speculationrules"> { "prerender": [{ "where": { "href_matches": "/article/*" }, "eagerness": "moderate" }], "prefetch": [{ "where": { "href_matches": "/*" }, "eagerness": "conservative" }] } </script>

eagerness控制触发时机,从immediate到conservative分几档,本质上是在「预测准确度」和「资源浪费」之间调旋钮。moderate大致对应用户鼠标悬停在链接上的时刻,这个时机点很好用——用户已经把注意力放到链接上了,点击概率大幅上升,但还没真的点。

预渲染的收益非常大,因为它是把整个页面在后台完整跑一遍,用户点进去几乎是瞬开。但代价也大:后台页面会执行 JS、可能发起接口请求、可能消耗 CPU。所以适用场景要挑,内容型站点的文章详情页、商品详情页这类「结构稳定、副作用小」的页面适合;带复杂交互或者有副作用的页面就要谨慎。这块能力目前的浏览器支持还在演进,用之前务必做特性检测和能力降级。

3. 图片与媒体资源的预加载实操

3.1 首屏关键图片的三层保障

首屏主图的加载,我一般会做三层配置,从发现时机、优先级、候选图三个维度一起下手。

第一层,把图片的<img>标签直接写进 HTML,不要用 JS 动态插入,也不要用 CSS 背景图。CSS 背景图要等 CSSOM 建好才被发现,JS 插入的更要等脚本执行,这两条路径都会大幅推迟下载起点。这是最基础也最有效的一招,很多项目性能差就差在这一步。

第二层,给这张图加fetchpriority="high",同时在<head>里补一条preload作为双保险:

<head> <link rel="preload" as="image" href="/img/hero-960.webp" imagesrcset="/img/hero-480.webp 480w, /img/hero-960.webp 960w, /img/hero-1440.webp 1440w" imagesizes="(max-width: 600px) 100vw, 50vw" fetchpriority="high"> </head> <body> <img src="/img/hero-960.webp" srcset="/img/hero-480.webp 480w, /img/hero-960.webp 960w, /img/hero-1440.webp 1440w" sizes="(max-width: 600px) 100vw, 50vw" fetchpriority="high" alt="首屏主视觉"> </body>

第三层,格式上优先用 WebP 或 AVIF,配合type属性让浏览器跳过不支持的格式。实测下来,同样视觉质量下 WebP 比 JPEG 小 25% 到 35%,AVIF 还能再小一截,但编码耗时和兼容性要权衡。我一般的策略是 AVIF 打头、WebP 兜底、JPEG 做最终降级,用<picture>组织。

三层叠下来,首屏主图的下载起点能从「CSS 解析完」提前到「HTML 头部解析到」,这一步在移动网络下能省几百毫秒。

3.2 轮播与长列表图片的预热策略

轮播图是个经典场景:第一张图显示中,第二张图马上要出现。如果等用户滑到第二张才开始下载,切换瞬间必然是空白。这时候可以在第一张图加载完成后,主动预热下一张。

纯前端做法是在第一张图的load事件里创建new Image()赋值src,或者用link rel="prefetch"。不过我更推荐用IntersectionObserver做判断——轮播容器进入视口之后,再触发后续图片的预加载,避免用户在页面顶部根本没滚到轮播区就开始下载。

长列表的图片预热则需要节流。用户快速滚动时,如果每进入视口一张图就预加载后面三张,滚动到列表中部时可能已经预加载了几十张。我的做法是设置一个预热窗口,只预加载当前视口下方一到两屏范围内的图片,超出范围的取消或延后。配合loading="lazy"让浏览器自己管懒加载,前端只负责在关键节点做「提前量」补充,职责划分清爽很多。

这里有个反直觉的点:loading="lazy"和预加载是可以共存的。lazy管的是「什么时候开始下载」,预加载管的是「提前多久开始」。我的经验是lazy保留,但在距离视口还有一段距离时就触发预热,让图片在真正进入视口前就已经在下载中,滚到位置时正好显示。纯靠lazy的默认阈值经常来不及。

3.3 视频与音频的 preload 属性

<video>和<audio>标签自带preload属性,取值有none、metadata、auto三档。这个和前面讲的link rel=preload是两套机制,很多人搞混。

preload="none"表示不预加载任何内容,等用户点了播放再说,最省流量。preload="metadata"只拉文件头部的元信息,拿到时长、尺寸、封面,播放时再缓冲,这是大多数场景的推荐值。preload="auto"让浏览器自己决定预加载多少,实践中它往往会拉不少数据,流量敏感场景要慎用。

<video preload="metadata" poster="/img/poster.webp" playsinline> <source src="/media/intro.webm" type="video/webm"> <source src="/media/intro.mp4" type="video/mp4"> </video>

需要提醒的是,移动端浏览器对preload="auto"的执行并不一致,很多情况下会忽略它、强制走省流量策略。所以别指望靠这个属性做「视频秒开」,真要秒开得用分段加载加首帧预载的方案,或者用 MSE 做分片缓冲。这是另一个话题,展开就偏了。

3.4 预加载抢带宽:一个必须验证的副作用

前面反复提到抢占,这里给个具体的排查方法。假设你给首屏主图加了preload,但 LCP 没降反升,大概率是预加载列表里混进了会抢带宽的东西。

第一步,打开开发者工具的网络面板,按优先级排序看请求列表。如果Highest或High优先级的请求数量明显多于首屏真正必需的资源数量,那就是优先级通胀了。

第二步,看这些高优先级请求的发起时间和主图的下载时间有没有重叠。带宽是共享的,几个大文件同时在跑,主图的速度必然被拖慢。

第三步,逐个摘掉可疑的预加载项,重新测 LCP。这个二分排查法虽然土,但非常有效。我一般会先摘掉体积最大的那几个,一次摘一个,测三次取中位数,避免噪声干扰。

实测下来,一个很常见的结论是:预加载字体文件的收益往往被高估。字体文件动辄几十上百 KB,预加载它会明显挤压主图带宽,而字体加载慢一点只影响文字渲染,对 LCP 的影响远小于主图。所以如果预算有限,宁可把带宽留给主图,字体用font-display: swap先降级显示。

4. 数据预加载:接口预热、缓存预热与骨架屏

4.1 路由跳转前的数据预取

资源预加载解决的是「静态文件来得慢」,数据预加载解决的是「接口回得慢」。后者的用户感知往往更明显,因为静态资源至少还有浏览器缓存,接口每次都要跑一遍服务端逻辑。

现代路由库基本都提供了预取能力。以使用比较广的 React 生态为例,路由 loader 模式可以在用户悬停链接时就开始拉数据,等真正跳转时数据已经在缓存里了。核心思路是把「路由跳转」和「数据获取」这两个动作解耦,让后者提前发生。

具体实现上,我一般会做两层:悬停触发一次轻量预取,只拉最关键的列表数据;跳转后如果发现还有缺失的详情数据,再补一次。这样既不会在悬停时就打一堆请求,又能保证主流程的数据已经就位。

手动实现也不复杂,核心是一个带缓存的请求函数:

const cache = new Map(); function prefetch(key, fetcher) { if (cache.has(key)) return cache.get(key); const p = fetcher().catch(err => { cache.delete(key); throw err; }); cache.set(key, p); return p; } // 悬停时触发 link.addEventListener('mouseenter', () => { prefetch(`/api/detail/${id}`, () => fetch(`/api/detail/${id}`).then(r => r.json()) ); }, { once: true });

几个细节要注意。once: true防止用户反复悬停重复触发;缓存里存的是 Promise 而不是结果,这样并发请求会自动去重;失败时要从缓存里删掉,否则一次失败会被缓存住,后续重试永远拿到 rejected 的 Promise。第三点我踩过,排查了很久才发现是个已缓存的失败 Promise 在作祟。

4.2 移动端的触摸信号怎么用

桌面端有mouseenter,移动端没有悬停,得换别的信号。我的经验是看touchstart和pointerdown。用户手指按下链接的瞬间,到手指抬起触发点击,中间有个几十到一百多毫秒的间隔,这段时间足够发起一个请求的握手了。

不过移动端更有效的信号其实是滚动速度骤降。用户在列表里快速滑动时,说明还在浏览;滑动速度突然降下来甚至停住,说明注意力落到了某几项上,这时候预热这几项对应的详情数据,命中率会高不少。

还有一种更保守的做法:只在 Wi-Fi 环境下做数据预取,蜂窝网络下关闭。实现上用navigator.connection.effectiveType判断,4g以上才启用:

const conn = navigator.connection; const canPrefetch = !conn?.saveData && (!conn?.effectiveType || /4g/.test(conn.effectiveType));

saveData这个字段是用户主动开启的省流量开关,优先级最高,看到它就一律不预取。这个细节很多项目会漏,但它直接影响用户对产品的观感。

4.3 服务端缓存预热:启动即热

前端预加载再努力,接口本身如果冷启动要跑两秒,用户照样等。所以预加载这件事是要贯穿全链路的,服务端这一层不能缺。

服务端缓存预热的典型做法是应用启动时加载热点数据。比如一个内容站点,可以在启动钩子里把访问量最高的若干篇文章查出来放进缓存,让第一批请求直接命中。预热逻辑要放在健康检查通过之前,避免预热没完成就被流量打进来。

async function warmup() { const hotIds = await db.query( 'SELECT id FROM articles ORDER BY views DESC LIMIT 200' ); await Promise.all( hotIds.map(({ id }) => cache.set(`article:${id}`, loadArticle(id), { ttl: 3600 }) ) ); }

这里有两个经验点。第一,预热的条目数量不要贪多,按实际 QPS 和缓存容量算,热数据通常遵循二八分布,抓头部就够了,尾部交给懒加载。第二,预热要有超时和失败容忍,不能因为某条数据查不到就卡住整个启动流程。我见过有项目把预热写成await Promise.all且不带任何容错,数据库抖一下整个服务起不来。

如果是多实例部署,还要考虑缓存击穿的问题——所有实例同时启动、同时预热同一批 key,瞬间给数据库造成一波压力。解决办法是加个随机延迟,或者只让一个实例负责预热、其余实例等通知。

4.4 骨架屏和预加载的分工

骨架屏经常被和预加载混为一谈,其实两者解决的是不同问题。预加载是「让数据早点到」,骨架屏是「数据没到时先给个结构占位」,一个管速度,一个管感知。

两者配合得好,效果是 1+1 大于 2:预加载保证了数据和图片在跳转后很快到位,骨架屏覆盖了那几百毫秒的空窗期,用户不会看到白屏或者布局跳动。但骨架屏本身也有成本,如果结构做得太复杂、动画太多,反而会拖慢首屏渲染,得不偿失。

我的做法是骨架屏只保留结构轮廓,不加渐变闪烁动画,或者让动画用一个纯 CSS 的animation跑在合成层上,不触发重排。另外骨架屏的尺寸要和真实内容严格对齐,否则数据到位那一刻会有一跳,观感很差。

5. 度量、监控与问题排查

5.1 用什么指标判断预加载是否有效

预加载做完了不看数据,等于没做。我的判断标准分三层。

第一层看命中率。预加载的资源里,有多少在短时间内被真正使用了?控制台警告能反映一部分,但生产环境得靠监控。可以监听PerformanceObserver的resource条目,比对预加载列表和实际请求列表,算出命中比例。低于 70% 就要回头检查预加载范围是不是铺太大了。

第二层看关键指标。首屏图片的预加载对应 LCP,交互相关的预加载对应 INP,路由跳转的数据预取对应页面切换的主观耗时。指标要按设备分档看,低端机和中高端机的结论经常相反——在高配机器上没问题的预加载,在低端机上可能因为 CPU 抢占反而更慢。

第三层看真实用户监控的分位数。平均值最会骗人,P75 和 P95 才能反映真实体验。一个优化如果只改善了 P50 没改善 P75,说明它只对原本就快的用户有效,价值有限。

5.2 常见问题速查表

现象可能原因排查方法处理方式
控制台提示资源被预加载但未使用预加载范围过宽看警告涉及哪些资源,核对是否条件渲染缩小范围或改成 prefetch
同一资源出现两次请求as写错或缺 crossorigin网络面板看两次请求的 initiator补全 as 与 crossorigin
LCP 不降反升预加载抢了关键资源带宽看高优先级请求数量摘掉大体积非关键预加载
字体仍未生效更快缺少 crossorigin比对两次字体请求的缓存键补 crossorigin
预加载接口未命中URL 参数不一致对比预加载 URL 与实际请求 URL统一 URL 生成逻辑
移动端流量消耗异常prefetch 未做网络判断检查 saveData 判断增加网络环境判断
路由切换仍卡只预取了资源没预取数据看切换时是否有接口请求补路由级数据预取

5.3 几条从实践中换来的心得

第一条,预加载列表要像依赖清单一样管理。不要散落在各个组件里随手写<link>,集中到一份配置里,随构建产物一起生成。我见过有项目预加载列表是半年前写的,里面好几个资源路径早就变了,一直在预加载 404。

第二条,新功能上线时同步检查预加载收益。页面结构变了,LCP 元素可能从图片变成了标题文字,原来给图片做的预加载就不再有价值,但代码还在跑,白白消耗带宽。每次大改版都花十分钟重测一遍,成本很低。

第三条,给预加载留一个开关。线上如果发现某个预加载策略导致异常,能一键关掉,比紧急发版快得多。实现上可以用配置中心或者 URL 参数控制,粗粒度即可。

第四条,别迷信工具给的「优化建议」。有些性能检测工具看到资源没预加载就建议加preload,但它不知道这个资源在你的业务里是不是关键资源。工具是参考,最终判断得靠你自己对页面渲染路径的理解。

第五条,也是我最想强调的:预加载的收益是有上限的。如果一个页面的瓶颈在服务端处理、在数据库慢查询、在巨大的 JS 包体积,那预加载再怎么优化也是杯水车薪。我遇到过团队花两周调预加载策略,最后发现真正的瓶颈是一个没加索引的查询,加个索引所有指标全好了。优化的顺序应该是:先量,找到真正的瓶颈,再决定用什么手段。预加载是很有效的工具,但它只解决「发现太晚」和「等待太长」这一类问题,不该承担它承担不了的责任。

我个人的习惯是,每次动预加载之前先在纸上画一遍页面的资源加载时间轴,标出每个资源的发现时刻和下载时刻,看看空档到底在哪。这个动作花不了几分钟,但能避免掉大部分盲目优化的功夫。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进