Script加载属性与资源预加载详解
我将详细解释async、defer、preload和prefetch属性,并通过一个交互式示例展示它们的工作原理。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Script加载属性与资源预加载详解</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
min-height: 100vh;
padding: 40px 20px;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
header {
background: #4e54c8;
color: white;
padding: 40px;
text-align: center;
position: relative;
}
h1 {
font-size: 2.8rem;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.subtitle {
font-size: 1.3rem;
max-width: 800px;
margin: 0 auto;
opacity: 0.9;
}
.content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 30px;
padding: 40px;
}
.section {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease;
}
.section:hover {
transform: translateY(-5px);
}
h2 {
color: #4e54c8;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
font-size: 1.8rem;
}
.attribute-card {
background: white;
border-radius: 10px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.05);
border-left: 4px solid;
}
.defer-card { border-left-color: #4e54c8; }
.async-card { border-left-color: #fdbb2d; }
.preload-card { border-left-color: #b21f1f; }
.prefetch-card { border-left-color: #1a2a6c; }
.attribute-title {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.attribute-name {
font-size: 1.5rem;
font-weight: bold;
margin-right: 15px;
}
.attribute-tag {
background: #e9ecef;
padding: 5px 10px;
border-radius: 5px;
font-family: monospace;
font-size: 0.9rem;
}
.attribute-description {
margin-bottom: 15px;
font-size: 1.1rem;
}
.timeline {
background: #e9ecef;
height: 10px;
border-radius: 5px;
margin: 20px 0;
position: relative;
overflow: hidden;
}
.timeline-event {
position: absolute;
top: 0;
height: 100%;
width: 0;
background: #4e54c8;
border-radius: 5px;
transition: width 1.5s ease-out;
}
.timeline-labels {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 0.9rem;
color: #6c757d;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.comparison-table th, .comparison-table td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
.comparison-table th {
background: #4e54c8;
color: white;
font-weight: normal;
}
.comparison-table tr:nth-child(even) {
background: #f8f9fa;
}
.controls {
display: flex;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap;
}
button {
padding: 14px 28px;
border: none;
border-radius: 8px;
background: #4e54c8;
color: white;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
flex: 1;
min-width: 200px;
}
button:hover {
background: #3f43a8;
transform: translateY(-3px);
box-shadow: 0 7px 15px rgba(0,0,0,0.1);
}
.demo-section {
background: #e9ecef;
padding: 25px;
border-radius: 10px;
margin-top: 20px;
}
.demo-output {
background: white;
padding: 20px;
border-radius: 8px;
min-height: 100px;
font-family: monospace;
white-space: pre-wrap;
margin-top: 15px;
box-shadow: inset 0 2px 5px rgba(0,0,0,0.05);
}
.key-points {
background: #eef7ff;
border-left: 4px solid #4e54c8;
padding: 20px;
margin: 25px 0;
border-radius: 0 8px 8px 0;
}
.key-points h3 {
color: #4e54c8;
margin-bottom: 15px;
}
.key-points ul {
padding-left: 25px;
}
.key-points li {
margin-bottom: 10px;
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
padding: 20px;
}
header {
padding: 30px 20px;
}
h1 {
font-size: 2.2rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Script加载与资源预加载详解</h1>
<p class="subtitle">深入理解 async、defer、preload 和 prefetch 的工作原理与应用场景</p>
</header>
<div class="content">
<div class="section">
<h2>Script 加载属性</h2>
<div class="attribute-card defer-card">
<div class="attribute-title">
<div class="attribute-name">defer</div>
<div class="attribute-tag"><script defer></div>
</div>
<div class="attribute-description">
延迟执行脚本。脚本会在HTML解析完成后、DOMContentLoaded事件前执行,按顺序执行。
</div>
<div class="timeline">
<div class="timeline-event" id="defer-timeline" style="width: 0%"></div>
</div>
<div class="timeline-labels">
<span>HTML解析开始</span>
<span>DOMContentLoaded</span>
</div>
<div class="key-points">
<h3>关键特性</h3>
<ul>
<li>不阻塞HTML解析</li>
<li>按脚本在文档中的顺序执行</li>
<li>在DOMContentLoaded事件前执行</li>
<li>适用于依赖DOM的脚本</li>
</ul>
</div>
</div>
<div class="attribute-card async-card">
<div class="attribute-title">
<div class="attribute-name">async</div>
<div class="attribute-tag"><script async></div>
</div>
<div class="attribute-description">
异步加载脚本。脚本下载不阻塞HTML解析,下载完成后立即执行(可能在HTML解析完成前)。
</div>
<div class="timeline">
<div class="timeline-event" id="async-timeline" style="width: 0%"></div>
</div>
<div class="timeline-labels">
<span>HTML解析开始</span>
<span>脚本下载完成立即执行</span>
</div>
<div class="key-points">
<h3>关键特性</h3>
<ul>
<li>下载不阻塞HTML解析</li>
<li>执行顺序无法保证(先下载完先执行)</li>
<li>适用于独立脚本(如分析统计)</li>
<li>可能阻塞DOMContentLoaded事件</li>
</ul>
</div>
</div>
<div class="demo-section">
<h3>加载行为演示</h3>
<div class="controls">
<button id="normal-btn">普通Script加载</button>
<button id="defer-btn">defer加载</button>
<button id="async-btn">async加载</button>
</div>
<div class="demo-output" id="script-output"></div>
</div>
</div>
<div class="section">
<h2>资源预加载属性</h2>
<div class="attribute-card preload-card">
<div class="attribute-title">
<div class="attribute-name">preload</div>
<div class="attribute-tag"><link rel="preload"></div>
</div>
<div class="attribute-description">
指示浏览器立即预加载指定资源,该资源在当前导航中肯定会用到。
</div>
<div class="key-points">
<h3>关键特性</h3>
<ul>
<li>高优先级加载</li>
<li>用于当前页面必定需要的资源</li>
<li>需指定as属性(如as="script"、as="style")</li>
<li>可搭配crossorigin属性</li>
</ul>
</div>
</div>
<div class="attribute-card prefetch-card">
<div class="attribute-title">
<div class="attribute-name">prefetch</div>
<div class="attribute-tag"><link rel="prefetch"></div>
</div>
<div class="attribute-description">
指示浏览器在空闲时预取资源,该资源可能在后续导航中使用。
</div>
<div class="key-points">
<h3>关键特性</h3>
<ul>
<li>低优先级加载</li>
<li>用于未来可能需要的资源</li>
<li>不会阻塞当前页面关键资源</li>
<li>适用于预加载下一页面的资源</li>
</ul>
</div>
</div>
<table class="comparison-table">
<thead>
<tr>
<th>属性</th>
<th>加载优先级</th>
<th>使用时机</th>
<th>适用资源</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>preload</strong></td>
<td>高(立即加载)</td>
<td>当前页面</td>
<td>字体、关键CSS/JS、首屏图片</td>
</tr>
<tr>
<td><strong>prefetch</strong></td>
<td>低(空闲时加载)</td>
<td>未来页面</td>
<td>下一页面的资源、非关键资源</td>
</tr>
</tbody>
</table>
<div class="demo-section">
<h3>预加载演示</h3>
<div class="controls">
<button id="preload-btn">预加载资源 (preload)</button>
<button id="prefetch-btn">预取资源 (prefetch)</button>
</div>
<div class="demo-output" id="preload-output"></div>
</div>
<div class="key-points">
<h3>使用建议</h3>
<ul>
<li><strong>关键JS</strong>:使用defer加载(保持顺序)</li>
<li><strong>独立JS</strong>:使用async加载(如分析脚本)</li>
<li><strong>关键CSS/字体</strong>:使用preload预加载</li>
<li><strong>下一页资源</strong>:使用prefetch预取</li>
<li>避免同时使用async和defer</li>
</ul>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const scriptOutput = document.getElementById('script-output');
const preloadOutput = document.getElementById('preload-output');
// 模拟脚本加载
function simulateScriptLoad(type) {
scriptOutput.textContent = '';
scriptOutput.textContent += `开始加载${type}脚本...\n`;
// 模拟HTML解析过程
setTimeout(() => {
scriptOutput.textContent += 'HTML解析中...\n';
if (type === '普通') {
scriptOutput.textContent += '脚本加载阻塞HTML解析...\n';
setTimeout(() => {
scriptOutput.textContent += '脚本加载完成,执行脚本\n';
scriptOutput.textContent += '继续HTML解析...\n';
scriptOutput.textContent += 'DOMContentLoaded事件触发\n';
}, 1000);
}
else if (type === 'defer') {
setTimeout(() => {
scriptOutput.textContent += 'HTML解析完成\n';
scriptOutput.textContent += '执行defer脚本\n';
scriptOutput.textContent += 'DOMContentLoaded事件触发\n';
}, 1000);
}
else if (type === 'async') {
setTimeout(() => {
scriptOutput.textContent += 'async脚本加载完成,立即执行(可能中断HTML解析)\n';
setTimeout(() => {
scriptOutput.textContent += 'HTML解析完成\n';
scriptOutput.textContent += 'DOMContentLoaded事件触发\n';
}, 500);
}, 800);
}
}, 300);
}
// 预加载演示
function simulatePreload(type) {
preloadOutput.textContent = '';
if (type === 'preload') {
preloadOutput.textContent += '开始预加载关键资源...\n';
preloadOutput.textContent += '资源优先级: 高\n';
setTimeout(() => {
preloadOutput.textContent += '资源已预加载完成\n';
preloadOutput.textContent += '当页面需要该资源时,可立即使用\n';
}, 800);
}
else if (type === 'prefetch') {
preloadOutput.textContent += '开始预取未来可能需要的资源...\n';
preloadOutput.textContent += '资源优先级: 低(浏览器空闲时加载)\n';
setTimeout(() => {
preloadOutput.textContent += '资源预取中...\n';
setTimeout(() => {
preloadOutput.textContent += '资源预取完成\n';
preloadOutput.textContent += '该资源将在后续导航中使用\n';
}, 1200);
}, 500);
}
}
// 时间轴动画
function animateTimeline(elementId, duration) {
const timeline = document.getElementById(elementId);
timeline.style.width = '0%';
setTimeout(() => {
timeline.style.transition = `width ${duration}ms ease-out`;
timeline.style.width = '100%';
}, 100);
}
// 绑定按钮事件
document.getElementById('normal-btn').addEventListener('click', () => {
simulateScriptLoad('普通');
animateTimeline('defer-timeline', 2000);
animateTimeline('async-timeline', 2000);
});
document.getElementById('defer-btn').addEventListener('click', () => {
simulateScriptLoad('defer');
animateTimeline('defer-timeline', 2000);
});
document.getElementById('async-btn').addEventListener('click', () => {
simulateScriptLoad('async');
animateTimeline('async-timeline', 1500);
});
document.getElementById('preload-btn').addEventListener('click', () => {
simulatePreload('preload');
});
document.getElementById('prefetch-btn').addEventListener('click', () => {
simulatePreload('prefetch');
});
});
</script>
</body>
</html>
详细解释
1. Script 标签属性
async 属性
- 作用:异步加载脚本
- 行为:
- 下载不阻塞HTML解析
- 脚本下载完成后立即执行(可能在HTML解析完成前)
- 多个async脚本执行顺序无法保证(先下载完先执行)
- 适用场景:独立脚本(如分析统计、广告脚本)
- 语法:
<script async src="script.js"></script>
defer 属性
- 作用:延迟执行脚本
- 行为:
- 下载不阻塞HTML解析
- 在HTML解析完成后、DOMContentLoaded事件前执行
- 多个defer脚本按它们在文档中的顺序执行
- 适用场景:依赖DOM的脚本,需要按顺序执行的脚本
- 语法:
<script defer src="script.js"></script>
2. 资源预加载属性
preload 属性
- 作用:立即预加载当前页面关键资源
- 特点:
- 高优先级加载
- 必须指定
as属性(如script、style、font) - 当前页面一定会使用的资源
- 语法:
<link rel="preload" href="font.woff2" as="font" crossorigin> - 适用场景:
- 关键字体文件
- 首屏关键CSS/JS
- 首屏大图
prefetch 属性
- 作用:在浏览器空闲时预取未来可能需要的资源
- 特点:
- 低优先级加载
- 不会阻塞当前页面关键资源
- 适用于后续导航可能需要的资源
- 语法:
<link rel="prefetch" href="next-page.js" as="script"> - 适用场景:
- 预加载下一页面的资源
- 预加载非关键资源(如图标、背景图)
3. 对比总结
| 属性 | 加载时机 | 执行/使用时机 | 优先级 | 顺序保证 |
|---|---|---|---|---|
async | 立即下载 | 下载完成后立即执行 | 高 | ❌ 无保证 |
defer | 立即下载 | HTML解析完成后执行 | 中 | ✅ 有保证 |
preload | 立即下载 | 当前页面需要时使用 | 最高 | - |
prefetch | 浏览器空闲时下载 | 后续页面需要时使用 | 最低 | - |
4. 最佳实践建议
- 关键JS:使用
defer加载(保持执行顺序) - 独立JS:使用
async加载(如分析脚本) - 关键CSS/字体:使用
preload预加载 - 下一页资源:使用
prefetch预取 - 避免混用:不要同时使用
async和defer属性 - 谨慎使用:
preload只用于关键资源,滥用会浪费带宽
通过合理使用这些属性,可以显著优化页面加载性能,提升用户体验。