Script 标签位置对页面加载的影响
script 放在 head 和 body 底部的区别及最佳实践
问题
Script 标签放在 <head> 中和 <body> 底部有什么区别?
解答
放在 <head> 中
<!DOCTYPE html>
<html>
<head>
<title>Script in Head</title>
<!-- 浏览器解析到这里会暂停 HTML 解析,下载并执行 JS -->
<script src="app.js"></script>
</head>
<body>
<!-- JS 执行完毕后才会渲染这部分内容 -->
<div id="app">Hello World</div>
</body>
</html>
问题:
- 阻塞 HTML 解析,页面白屏时间长
- JS 执行时 DOM 还未构建,无法操作 DOM 元素
// app.js - 放在 head 中执行
const app = document.getElementById('app');
console.log(app); // null,因为 DOM 还没解析到这个元素
放在 <body> 底部
<!DOCTYPE html>
<html>
<head>
<title>Script at Bottom</title>
</head>
<body>
<div id="app">Hello World</div>
<!-- DOM 已经解析完成,可以安全操作 -->
<script src="app.js"></script>
</body>
</html>
// app.js - 放在 body 底部执行
const app = document.getElementById('app');
console.log(app); // <div id="app">Hello World</div>
使用 defer 和 async
现代方案是在 <head> 中使用 defer 或 async 属性:
<!DOCTYPE html>
<html>
<head>
<title>Modern Approach</title>
<!-- defer: 并行下载,DOM 解析完成后按顺序执行 -->
<script defer src="app.js"></script>
<!-- async: 并行下载,下载完立即执行(不保证顺序) -->
<script async src="analytics.js"></script>
</head>
<body>
<div id="app">Hello World</div>
</body>
</html>
执行时机对比
普通 script(head):
HTML 解析 → 暂停 → 下载 JS → 执行 JS → 继续解析 HTML → DOMContentLoaded
普通 script(body 底部):
HTML 解析完成 → 下载 JS → 执行 JS → DOMContentLoaded
defer:
HTML 解析(同时下载 JS)→ 解析完成 → 执行 JS → DOMContentLoaded
async:
HTML 解析(同时下载 JS)→ 下载完立即执行(可能打断解析)→ DOMContentLoaded
完整示例
<!DOCTYPE html>
<html>
<head>
<title>Script Loading Demo</title>
<!-- 第三方统计,不依赖 DOM,用 async -->
<script async src="https://example.com/analytics.js"></script>
<!-- 主应用代码,需要 DOM,用 defer -->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
关键点
- head 中普通 script:阻塞 HTML 解析,无法操作后面的 DOM
- body 底部:不阻塞页面渲染,DOM 已就绪,但下载时机晚
- defer:并行下载,DOM 解析后按顺序执行,推荐用于主要脚本
- async:并行下载,下载完立即执行,适合独立的第三方脚本
- 最佳实践:使用
defer放在<head>中,兼顾下载时机和执行时机
目录