2024-12-20|
HTML性能优化资源加载浏览器
前端资源加载属性对比:async、defer、preload 与 prefetch
对比 async、defer、preload、prefetch、preconnect 等脚本与资源加载属性,并提供可直接使用的 HTML 示例。
前端资源加载属性对比
前端性能优化中经常会遇到两类配置:一类控制脚本什么时候下载、执行,另一类提示浏览器提前建立连接或获取资源。它们解决的问题不同,不能简单地认为“越早加载越快”。
你提到的 refer/sync 通常是指脚本的 defer/async 属性,下面将它们和常用的 Resource Hints(资源提示)放在一起比较。
一张表看懂异同
| 属性或写法 | 作用对象 | 下载时机 | 执行或生效时机 | 是否阻塞 HTML 解析 | 适用场景 | 主要注意点 |
|---|---|---|---|---|---|---|
<script src="app.js" defer> | 经典脚本 | HTML 解析同时下载 | HTML 解析完成后,按文档顺序执行 | 否 | 有依赖关系的业务脚本、入口脚本 | 脚本会在 DOMContentLoaded 前执行;只对外部经典脚本有意义 |
<script src="app.js" async> | 经典脚本 | HTML 解析同时下载 | 下载完成后立即执行 | 执行时会短暂阻塞 | 独立的统计、广告、监控脚本 | 多个 async 脚本执行顺序不确定,不能依赖 DOM 或其他脚本 |
<script src="app.js"> | 经典脚本 | 解析到标签时下载 | 下载完成后立即执行 | 是 | 必须同步执行的极少数脚本 | 会暂停 HTML 解析,通常不建议用于大型脚本 |
<script type="module" src="app.js"> | ES Module 脚本 | 默认异步下载 | 默认延迟到解析完成后执行 | 否 | 现代模块化应用 | 默认行为接近 defer;模块依赖会继续被解析和加载 |
<script nomodule src="legacy.js"> | 经典脚本 | 由不支持 Module 的浏览器下载 | 按普通经典脚本规则执行 | 通常会阻塞 | 为旧浏览器提供降级脚本 | 通常与 type="module" 成对使用 |
<link rel="preload" as="script"> | 当前页面很快需要的资源 | 页面解析后立即高优先级下载 | 资源被真正引用时使用 | 否 | 首屏关键字体、CSS、脚本、图片 | 必须填写正确的 as;预加载后长期不用会浪费带宽 |
<link rel="modulepreload"> | ES Module 及其依赖 | 尽早下载模块资源 | 模块执行时直接使用缓存资源 | 否 | 需要尽快启动的模块化应用 | 只适用于模块资源,不能替代普通 preload as="script" 的所有场景 |
<link rel="prefetch" as="script"> | 未来页面可能需要的资源 | 浏览器空闲时低优先级下载 | 未来导航或交互需要时使用 | 否 | 下一路由、下一步操作的资源 | 资源不一定会下载;不要用它抢占当前页面关键资源 |
<link rel="preconnect" href="https://cdn.example.com"> | 目标源站连接 | 尽早完成 DNS、TCP、TLS 建连 | 后续请求可复用连接 | 否 | 即将请求的跨域 CDN、字体或 API | 只给真正会访问的源站使用,过多连接会浪费资源 |
<link rel="dns-prefetch" href="//cdn.example.com"> | 目标域名 | 空闲时优先解析 DNS | 后续请求复用 DNS 结果 | 否 | 仅需提前解析、但不确定是否立即请求的第三方域名 | 只解决 DNS,不建立 TCP/TLS;优先级低于 preconnect |
fetchpriority="high" | 图片、脚本、链接等请求 | 浏览器调度时提高优先级 | 影响资源请求调度 | 否 | LCP 主图、首屏关键资源 | 只是提示,不是强制命令;滥用高优先级会降低整体性能 |
fetchpriority="low" | 图片、脚本、链接等请求 | 浏览器调度时降低优先级 | 影响资源请求调度 | 否 | 非首屏图片、低优先级脚本 | 不代表一定延迟加载,只是降低调度优先级 |
<img loading="lazy"> / <iframe loading="lazy"> | 图片、iframe | 接近视口时再下载 | 下载完成后正常展示或加载 | 否 | 长列表、首屏以下图片和 iframe | 不适合首屏 LCP 图片;首屏图片可使用 loading="eager" |
<img decoding="async"> | 图片解码 | 不改变网络下载时机 | 允许浏览器异步解码 | 否 | 不希望图片解码阻塞渲染的场景 | 只是解码提示,不等于网络异步加载 |
blocking="render" | 脚本、样式等资源 | 按元素自身规则下载 | 下载后允许阻塞渲染直到资源处理完成 | 可能阻塞渲染 | 必须在首屏绘制前完成的动态资源 | 谨慎使用,阻塞渲染会直接影响 FCP/LCP |
crossorigin="anonymous" | 跨域字体、脚本、图片等 | 不改变下载时机 | 以 CORS 模式请求资源 | 否 | 跨域字体、需要 Timing-Allow-Origin 的资源 | preload 和实际引用必须使用一致的 crossorigin,否则可能重复下载 |
referrerpolicy="no-referrer" 等 | 资源请求 | 不改变下载时机 | 控制请求发送的 Referer 信息 | 否 | 第三方资源、隐私和安全控制 | 它控制来源信息,不控制资源优先级或执行顺序 |
脚本加载示例
defer:保持顺序,等待解析完成
HTML<script src="/js/vendor.js" defer></script>
<script src="/js/app.js" defer></script>
两个脚本可以并行下载,但会按照它们在 HTML 中出现的顺序执行。适合 app.js 依赖 vendor.js 的情况。
async:下载完成就执行
HTML<script src="https://analytics.example.com/analytics.js" async></script>
<script src="https://ads.example.com/ads.js" async></script>
两个脚本谁先下载完成谁先执行,因此它们应当彼此独立,不要依赖执行顺序,也不要假设目标 DOM 已经完成渲染。
Module 与旧浏览器降级
HTML<script type="module" src="/js/app.mjs"></script>
<script nomodule defer src="/js/legacy.js"></script>
支持 ES Module 的浏览器使用 app.mjs,旧浏览器使用 legacy.js。Module 脚本默认具有延迟执行特征。
Resource Hints 示例
HTML<head>
<!-- 当前页面首屏一定会用到的字体:高优先级提前加载 -->
<link
rel="preload"
href="/fonts/inter-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<!-- 当前页面很快会执行的模块 -->
<link rel="modulepreload" href="/js/app.mjs" />
<!-- 下一路由可能使用的资源:空闲时再取 -->
<link rel="prefetch" href="/js/settings.mjs" as="script" />
<!-- 即将访问的跨域 CDN:提前完成连接握手 -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<!-- 只提前解析 DNS,成本低于 preconnect -->
<link rel="dns-prefetch" href="//img.example.com" />
</head>
preload 与实际引用要匹配
HTML<link
rel="preload"
href="/fonts/inter-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<style>
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
font-display: swap;
}
</style>
字体的预加载和 @font-face 请求需要保持相同的 URL、CORS 模式和资源类型,否则浏览器可能把它们当成两个请求。
图片加载示例
HTML<!-- 首屏最大的图片:不要 lazy,适当提高请求优先级 -->
<img
src="/images/hero.webp"
width="1280"
height="720"
fetchpriority="high"
decoding="async"
alt="首页主视觉"
/>
<!-- 首屏以下的图片:接近视口时再加载 -->
<img
src="/images/article-cover.webp"
width="800"
height="450"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="文章配图"
/>
图片最好显式设置 width 和 height,这样浏览器可以提前预留空间,减少布局偏移(CLS)。
选择建议
- 业务入口脚本优先考虑
defer或type="module",需要保持顺序时不要使用async。 - 独立统计、监控脚本可以使用
async,避免它们影响主业务脚本。 - 只有当前页面很快且确定会使用的关键资源才使用
preload。 - 下一页面或未来交互才会使用的资源使用
prefetch,并接受它可能不执行的事实。 - 只对少量确定会访问的跨域源使用
preconnect;其他第三方域名可考虑dns-prefetch。 - 用
loading="lazy"延后非首屏图片,用fetchpriority="high"明确首屏关键图片,避免同时把大量资源标记为高优先级。 - 这些属性大多是浏览器提示,不是绝对命令;最终效果需要结合 Performance 面板、LCP、CLS 和真实网络环境验证。