资源加载与优先级
preload、prefetch、modulepreload 区别与 Priority Hints
一、资源加载优先级概览
1.1 浏览器默认优先级
| 资源类型 | 默认优先级 | 说明 |
|---|---|---|
| HTML | Highest | 文档本身 |
| CSS(阻塞渲染) | High | 首屏样式 |
| 字体 | High | 避免 FOUT |
| 同步 JS | High | 阻塞解析 |
| 图片(视口内) | High | LCP 相关 |
| 预加载资源 | High | preload |
| 异步/延迟 JS | Medium | 不阻塞 |
| 图片(视口外) | Low | 懒加载 |
| prefetch | Lowest | 空闲时加载 |
1.2 加载流程
HTML 解析 → 发现资源 → 加入队列 → 按优先级调度 → 发起请求
│
├── preload → 立即高优先级
├── 同步 script → 阻塞,高优先级
├── CSS → 高优先级
├── 图片 → 视口内高,视口外低
└── prefetch → 空闲时低优先级二、preload 预加载
2.1 语法与用途
html
<!-- 预加载关键资源,立即高优先级请求 -->
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/main.js" as="script">
<link rel="preload" href="/font.woff2" as="font" crossorigin>
<link rel="preload" href="/hero.webp" as="image">2.2 as 属性
| as 值 | 用途 |
|---|---|
| script | JS 文件 |
| style | CSS 文件 |
| font | 字体文件(需 crossorigin) |
| image | 图片 |
| fetch | fetch/XHR 请求 |
| document | iframe 文档 |
2.3 使用场景
- 关键 CSS:首屏渲染必需的样式
- 关键字体:避免文字闪烁 (FOUT)
- LCP 图片:首屏大图提前加载
- 关键 JS:首屏交互必需
2.4 注意事项
html
<!-- ❌ 错误:preload 后未使用,浪费带宽 -->
<link rel="preload" href="/unused.js" as="script">
<!-- ✅ 正确:preload 的字体需 crossorigin -->
<link rel="preload" href="/font.woff2" as="font" crossorigin>
<!-- ✅ 与 media 配合,按需加载 -->
<link rel="preload" href="/large.css" as="style" media="(min-width: 768px)">三、prefetch 预获取
3.1 语法与用途
html
<!-- 低优先级,浏览器空闲时加载,用于下一页/后续可能用到的资源 -->
<link rel="prefetch" href="/next-page.js">
<link rel="prefetch" href="/next-page.css">3.2 与 preload 的区别
| 对比项 | preload | prefetch |
|---|---|---|
| 优先级 | 高,立即加载 | 低,空闲时加载 |
| 用途 | 当前页关键资源 | 下一页/后续可能用到的资源 |
| 缓存 | 用于当前页 | 存入 HTTP 缓存,下次用 |
| 时机 | 发现即请求 | 主资源加载完后 |
3.3 使用场景
- 分页场景:预加载下一页的 JS/CSS
- 路由预加载:SPA 预加载可能访问的路由 chunk
- 图片预加载:用户 hover 时 prefetch 大图
javascript
// 路由预加载示例
router.beforeEach((to, from, next) => {
if (to.name === 'Detail') {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = '/chunks/detail.js';
document.head.appendChild(link);
}
next();
});四、modulepreload 模块预加载
4.1 语法与用途
html
<!-- 预加载 ES Module,比 preload as="script" 更高效 -->
<link rel="modulepreload" href="/module-a.js">
<link rel="modulepreload" href="/module-b.js">4.2 与 preload 的区别
- modulepreload:专为 ES Module 设计,会递归预加载
import的依赖 - preload as="script":只加载单个文件,不处理依赖
html
<!-- 主模块依赖 a.js、b.js,modulepreload 会一并预加载 -->
<link rel="modulepreload" href="/main.js">
<script type="module" src="/main.js"></script>4.3 使用场景
- 使用 ES Module 的 SPA
- 需要提前加载模块依赖树
五、fetchpriority 优先级提示
5.1 语法(Chrome 101+)
html
<!-- 提升 LCP 图片优先级 -->
<img src="/hero.jpg" fetchpriority="high" alt="Hero">
<!-- 降低非关键图片优先级 -->
<img src="/decoration.png" fetchpriority="low" alt="Decoration">
<!-- 预加载时指定优先级 -->
<link rel="preload" href="/font.woff2" as="font" crossorigin fetchpriority="high">5.2 取值
| 值 | 说明 |
|---|---|
| high | 提升优先级 |
| low | 降低优先级 |
| auto | 默认,浏览器自动决定 |
5.3 使用场景
- LCP 图片:
fetchpriority="high"确保首屏大图优先 - 首屏以下图片:
fetchpriority="low"避免抢占带宽 - 懒加载占位图:
fetchpriority="low"或省略
六、dns-prefetch 与 preconnect
6.1 dns-prefetch
html
<!-- 提前解析 DNS,减少后续请求延迟 -->
<link rel="dns-prefetch" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://api.example.com">6.2 preconnect
html
<!-- 提前建立 TCP + TLS 连接(含 DNS) -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>6.3 对比
| 方式 | DNS | TCP | TLS |
|---|---|---|---|
| dns-prefetch | ✅ | ❌ | ❌ |
| preconnect | ✅ | ✅ | ✅ |
- 关键第三方域名用 preconnect
- 非关键第三方用 dns-prefetch(更省资源)
七、懒加载策略
7.1 原生 loading="lazy"
html
<!-- 视口外图片延迟加载 -->
<img src="/image.jpg" loading="lazy" alt="...">
<!-- iframe 懒加载 -->
<iframe src="/embed" loading="lazy"></iframe>7.2 IntersectionObserver 懒加载
javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src || img.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));7.3 动态 import 懒加载
javascript
// 路由级懒加载
const Detail = () => import('./Detail.vue');
// 组件内懒加载
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'));八、资源加载决策流程
发现资源
│
├── 有 preload? ──Yes──▶ 高优先级立即请求
│
├── 有 prefetch? ──Yes──▶ 低优先级,空闲时请求
│
├── 有 modulepreload? ──Yes──▶ 预加载模块及依赖
│
├── 有 fetchpriority? ──Yes──▶ 调整优先级
│
└── 默认 ──▶ 按资源类型分配优先级九、高频面试题
Q1: preload 和 prefetch 的区别?
答案:preload 高优先级、立即加载,用于当前页关键资源;prefetch 低优先级、空闲时加载,用于下一页或后续可能用到的资源。
Q2: modulepreload 和 preload 的区别?
答案:modulepreload 专为 ES Module 设计,会递归预加载 import 的依赖;preload 只加载单个文件。
Q3: 如何优化 LCP?
答案:对 LCP 图片使用 fetchpriority="high" 或 rel="preload" as="image";对关键字体使用 rel="preload" as="font" crossorigin;对关键第三方域名使用 rel="preconnect"。
Q4: dns-prefetch 和 preconnect 的区别?
答案:dns-prefetch 只解析 DNS;preconnect 完成 DNS + TCP + TLS 全流程,适合关键第三方。