资源优先级与 Priority Hints:精准控制浏览器加载策略
浏览器有自己的资源加载优先级策略,但这个策略通常不是为你的应用优化的。结果经常是:
- 关键样式表被非关键脚本延迟了
- 首屏字体比首屏图片更慢加载
- 某个第三方脚本抢占了关键资源的带宽
幸好,现代浏览器给了开发者很多工具来"教导"它正确的优先级。
1. 浏览器的默认加载优先级
浏览器不是随意加载资源的,有一套隐形的优先级:
| 资源类型 | 默认优先级 | 典型用途 |
|---|---|---|
| HTML | 最高 | 页面骨架 |
| CSS | 高 | 渲染阻塞 |
| 同步脚本 | 高 | 关键逻辑 |
| 字体 | 中 | 文本显示 |
| 异步脚本 | 低 | 分析、追踪 |
| 图片 | 最低 | 视觉内容 |
问题是,这套优先级对你的应用不一定适用。比如:
- 首屏的关键字体比下面的普通图片更重要
- 某个脚本看起来是"助手",实际上是首屏交互必须
所以你需要手动调整。
2. preload:声明关键资源
用 <link rel="preload"> 告诉浏览器"这个资源我马上要用":
<!-- 关键的 CSS -->
<link rel="preload" href="/critical.css" as="style">
<link rel="stylesheet" href="/critical.css">
<!-- 首屏字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- 首屏关键脚本 -->
<link rel="preload" href="/critical-lib.js" as="script">
什么时候用 preload:
- 首屏展示的字体文件
- 关键性能路径上的脚本(不是异步脚本)
- 重要的背景图片(如果在 CSS 中而不是 HTML 中定义)
注意:preload 不是"异步加载",而是"提高优先级"。资源依然可能在渲染时被需要。
3. prefetch:提前加载"可能需要"的资源
<link rel="prefetch"> 的意思是"用户下一步可能需要这些":
<!-- 用户可能会点击下一页 -->
<link rel="prefetch" href="/next-page.js">
<!-- 用户可能会悬停到这个 tab -->
<link rel="prefetch" href="/dashboard-data.json">
特点:
- 使用浏览器的空闲时间加载(不竞争带宽)
- 优先级最低,不会影响关键资源
- 如果用户离开页面,加载会被中断
什么时候用:
- 路由预加载
- 用户下一步可能访问的数据
- 相关资源(比如评论组件的资源)
避免过度 prefetch:如果全页面都 prefetch,反而会浪费带宽。
4. preconnect:提前建立连接
某些资源来自第三方域名(API、CDN、字体服务)。每次首次请求都要经历 DNS lookup、TCP 握手。
<link rel="preconnect"> 可以提前完成这些:
<!-- 提前连接字体服务 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 提前连接 API -->
<link rel="preconnect" href="https://api.example.com">
效果是隐形但有效的,通常能节省 100-300ms。
注意:preconnect 会占用系统连接数,不要滥用。通常 3-4 个第三方域就够了。
5. dns-prefetch:纯 DNS 查询加速
如果你只想加速 DNS 查询(不建立完整连接):
<link rel="dns-prefetch" href="https://analytics.example.com">
这比 preconnect 更轻量,适合次要资源。
6. fetchpriority:细粒度的加载优先级(新特性)
HTML 5 新标准 fetchpriority 可以在 <script>、<link>、<img> 标签上直接设定优先级:
<!-- 最高优先级:关键脚本 -->
<script src="/webpack.js" fetchpriority="high"></script>
<!-- 低优先级:非关键分析脚本 -->
<script src="/analytics.js" fetchpriority="low" async></script>
<!-- 首屏关键图片 -->
<img src="/hero.jpg" fetchpriority="high">
<!-- 下面的图片可以延迟 -->
<img src="/secondary.jpg" fetchpriority="low" loading="lazy">
兼容性:Chrome 96+, Edge 96+, Opera 82+(2024 年底主流浏览器都支持了)。
7. 实战案例:优化一个"较差"的页面
现状
- 首屏 LCP 4.5s(目标 2.5s)
- 关键字体延迟加载
- 分析脚本竞争带宽
改进清单
<!-- 1. preload 关键字体 -->
<link rel="preload" href="/fonts/Inter-Medium.woff2" as="font" type="font/woff2" crossorigin>
<!-- 2. preconnect 第三方源 -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- 3. 关键 CSS 直接内联或 preload -->
<link rel="preload" href="/critical.css" as="style">
<!-- 4. 高优先级关键脚本 -->
<script src="/app.js" fetchpriority="high"></script>
<!-- 5. 低优先级分析脚本 -->
<script src="/gtag.js" fetchpriority="low" async></script>
<!-- 6. 首屏图片设置高优先级,其他用 lazy -->
<img src="/hero.jpg" fetchpriority="high">
<img src="/secondary.jpg" loading="lazy">
预期提升
- 字体加载提前 800ms
- 关键资源带宽竞争减少
- LCP 从 4.5s 降低到 2.8s
8. 常见误区
误区 1:盲目 preload 所有资源
preload 有成本,多了反而拖累性能。只 preload 关键路径上的资源。
误区 2:与 async/defer 混淆
async:加载时不阻塞,执行时阻塞渲染defer:加载不阻塞,执行延迟到 DOM 解析完fetchpriority:优先级,不改变加载和执行时机
误区 3:忘记处理第三方脚本
分析、广告、实时通讯脚本通常来自第三方。给它们用 fetchpriority="low"。
9. 测量与监控
用 Performance API 验证优化效果:
// 监控字体加载时间
performance.measure('font-load', 'navigationStart', 'responseEnd')
// 看资源加载顺序
const entries = performance.getEntriesByType('resource')
entries.forEach(entry => {
console.log(`${entry.name}: ${entry.duration.toFixed(0)}ms`)
})
关键是要看优化前后的 LCP 和 FID,而不是虚荣指标。
10. 最佳实践清单
- 识别首屏关键资源(通常是字体、关键 CSS、某些脚本)
- 为关键资源使用
preload - 为第三方域名使用
preconnectordns-prefetch - 用
fetchpriority标记关键 vs 非关键资源 - 为下一页资源使用
prefetch(如果有路由) - 定期测量 LCP、FID,验证改进
- 避免过度使用 hint,维持 3-5 个关键优化


