三种资源获取方式的语义和时机差异:prefetch、preload、new Image()
一句话区分
preload:告诉浏览器"这个资源当前页面很快就要用,尽早加载"prefetch:告诉浏览器"这个资源这页暂时不用,但下一个页面/后续交互可能会用,空闲时再下"new Image().src:不是浏览器资源提示,而是你自己主动发起真实图片请求
一、preload
html
<link rel="preload" as="image" href="/banner.jpg">特点
高优先级提示
- 浏览器在解析 HTML 早期就能看到它
- 会尽早把它加入当前页面的关键资源队列
- 适合首屏很快会出现的图片
面向"当前导航"
- 它服务的是当前页面,而不是下一页
- 如果这个资源确实马上会被
<img>、CSS 背景图等使用,收益很明显
有明确语义
as="image"告诉浏览器这是图片资源- 浏览器会按资源类型做更合理的调度、缓存和优先级管理
- 不同
as值(image、style、script、font等)的优先级策略也不同
适用场景
- 首屏 banner 图
- 首屏背景图
- LCP(Largest Contentful Paint)图片
- 路由切进来后马上展示的大图
风险
- 不要滥用
- 如果一次 preload 很多张图,会抢占网络连接,反而拖慢真正关键的 JS/CSS/API
- 如果 preload 的图片最终没被用到,会产生浪费
二、prefetch
html
<link rel="prefetch" as="image" href="/next-page-cover.jpg">特点
低优先级、空闲时下载
- 浏览器通常会在当前页面关键资源都差不多完成后,再考虑它
- 它更像"顺手提前缓存"
面向"未来可能会用"
- 更适合下一页、下一步交互、未来可能展示的资源
- 不是为了当前页面立即渲染
提示性更强,约束更弱
- 浏览器可以选择做,也可以因为网络状况、省流模式、优先级策略而弱化甚至忽略
- 不像 preload 那么"强提醒"
适用场景
- 首页提前准备详情页首图
- 列表页提前准备用户最可能点击的下一页图片
- 多步流程里下一步会出现的插图
风险
- 它不保证一定马上下载
- 不适合拿来解决"当前页面马上要显示但来不及加载"的问题
- 在弱网下,浏览器可能基本不理它
三、new Image().src
js
const img = new Image()
img.src = '/cover.jpg'或者项目里这种写法:
js
const warmupImage = (src) => new Promise((resolve) => {
const image = new Image()
const done = () => resolve()
image.onload = done
image.onerror = done
image.src = src
})特点
这是实际请求,不是提示
- 一旦
src被赋值,浏览器就会真正开始请求图片 - 不是建议,不是 hint,而是你自己发起加载
- 从浏览器角度看,这和你写
<img src="/cover.jpg">没有本质区别
- 一旦
由业务代码控制时机
- 你可以在点击后、页面挂载后、空闲时、滚动到某区域后再加载
- 也可以自己控制并发、重试、分批预热
- 项目里的
warmupImagesSimple就是把这个能力封装成了可配并发数的批量预取
更灵活,但语义更弱
- 浏览器不知道它是"当前关键资源"还是"未来可能资源"
- 只是一个普通图片请求
- 优先级调度主要取决于浏览器对图片请求的默认策略,以及触发时机
适用场景
- 你想自己控制图片预热节奏
- 你要分批加载,避免一次性打满连接
- 你希望在某个交互后手动缓存下一批图片
- 图片列表较多,不适合全塞到
<head>
风险
- 如果你在很早的时机对很多图片都
new Image(),其实和"暴力并发请求"没区别 - 它不会天然比 preload 更聪明,反而更依赖你的调度策略
- 写不好容易和页面本身的关键资源争抢带宽
四、核心区别:谁在做调度
| preload | prefetch | new Image().src | |
|---|---|---|---|
| 调度者 | 浏览器 | 浏览器 | 应用代码 |
| 触发方式 | 声明式标签 | 声明式标签 | 脚本主动调用 |
| 优先级 | 高 | 低 | 普通图片请求 |
| 语义 | 当前页关键 | 未来可能用 | 无额外语义 |
| 控制力 | 弱(交给浏览器) | 弱(交给浏览器) | 强(自己控制) |
| 加载保证 | 浏览器会尽快加载 | 不保证,空闲时才做 | 立即发起实际请求 |
这就是三者最本质的差异:
- preload:你给出"当前关键资源"信号,浏览器尽早拉取
- prefetch:你给出"未来可能用到"信号,浏览器空闲时再处理
- new Image().src:你不是在"提示"浏览器,而是在"直接下命令发请求"
五、放到这个项目里怎么理解
这个项目实际上在演示三种不同策略:
A. rel="preload"
preloadImagesSimple 插件做的事情:把图片列表以 <link rel="preload"> 的形式注入到 HTML 的 <head> 里。
适合:
- 当前页马上要展示的几张关键图
- 想在进页面前就把关键图请求发出去
这个项目首页不展示图片,进入 /gallery 时再看图片,正好方便观察:
- preload 是否在页面真正渲染前就已经开始请求
B. rel="prefetch"
preloadImages 插件支持配置 attrs.rel 为 'prefetch',把 <link rel="preload"> 换成 <link rel="prefetch">。
如果改成 prefetch,你会发现:
- 请求通常更靠后
- 优先级更低
- 更像"浏览器有空才帮你存一下"
C. new Image().src
warmupImagesSimple 插件的本质:
- 扫出图片列表
- 注入一段脚本
- 用
new Image()分批请求 - 每批并发数可控
它更像"程序员自己做资源调度器"。
六、该怎么选
选 preload
当资源满足这两个条件:
- 当前页面很快就会用到
- 它足够关键,值得提前抢占下载时机
比如:首屏主视觉图、LCP 图。
选 prefetch
当资源满足这两个条件:
- 当前页面不急用
- 下一跳或后续交互大概率会用到
比如:列表页跳详情页的头图。
选 new Image().src
当你需要:
- 手动控制时机
- 手动控制并发
- 对大量图片做渐进式缓存预热
比如:相册页下一批图片、长列表、游戏素材、故事页分章节图片。
一个最实用的判断标准
你可以直接按这个思路判断:
马上展示 →
preload以后可能展示 →
prefetch我要自己掌控加载节奏 →
new Image().src
七、容易混淆的点:preload vs new Image()
preload 和 new Image() 都可能"提前把图片下下来",但它们不是一回事:
- preload 是 HTML 层的资源提示,更早进入浏览器资源调度。浏览器在解析 HTML 的
<head>阶段就能看到它,此时页面 body 还没开始渲染。 - new Image() 是 JS 层主动发请求,什么时候发、发多少,由你的代码决定。但它需要等 JS 下载、解析、执行到那行代码时才会触发。
所以:
- 少量关键图,优先考虑 preload
- 大量非关键图,通常更适合 new Image() 分批预热
- 下一页资源,优先考虑 prefetch
八、总结
这个项目最值得讨论的不是"哪个插件更高级",而是这三种策略分别对应三类目标:
| 策略 | 核心目标 | 类比 |
|---|---|---|
| preload | 加速当前页关键图片首屏展示 | 提前把菜端到桌上 |
| prefetch | 为未来页面或后续交互做低优先级准备 | 先买好菜放冰箱 |
| new Image() | 用业务代码主动做缓存预热,可控并发 | 自己下厨,按自己的节奏做 |
在真实项目里,这三种方式经常不是互斥的,而是组合使用:
- 首屏 1~2 张关键图用 preload
- 下一页高概率资源用 prefetch
- 其余批量图片用 new Image() 在空闲时分批预热
本文档与项目三插件对应关系:
preload→preloadImagesSimple/preloadImagesprefetch→preloadImages(配置attrs.rel = 'prefetch')new Image().src→warmupImagesSimple
