脚本加载优化
defer、async 和动态创建 script 的区别与使用场景
问题
如何优化页面中 JavaScript 脚本的加载?defer、async 和动态创建 script 有什么区别?
解答
普通 script 加载
<script src="app.js"></script>
浏览器遇到 <script> 标签时会:
- 暂停 HTML 解析
- 下载脚本
- 执行脚本
- 继续解析 HTML
这会阻塞页面渲染。
defer
<script src="app.js" defer></script>
- 下载与 HTML 解析并行
- 在 HTML 解析完成后、
DOMContentLoaded事件前执行 - 多个 defer 脚本按顺序执行
<!-- 按 1, 2, 3 顺序执行 -->
<script src="1.js" defer></script>
<script src="2.js" defer></script>
<script src="3.js" defer></script>
async
<script src="app.js" async></script>
- 下载与 HTML 解析并行
- 下载完成后立即执行(会暂停 HTML 解析)
- 多个 async 脚本执行顺序不确定
<!-- 执行顺序取决于下载完成时间 -->
<script src="1.js" async></script>
<script src="2.js" async></script>
<script src="3.js" async></script>
动态创建 script
// 基本用法
function loadScript(src) {
const script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
}
// 带回调的版本
function loadScript(src, callback) {
const script = document.createElement('script');
script.src = src;
script.onload = () => callback(null);
script.onerror = () => callback(new Error(`Failed to load ${src}`));
document.body.appendChild(script);
}
// Promise 版本
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}
// 使用
loadScript('https://example.com/lib.js')
.then(() => console.log('loaded'))
.catch(() => console.log('failed'));
动态创建的 script 默认是 async 行为,可以手动设置:
const script = document.createElement('script');
script.src = 'app.js';
script.async = false; // 按插入顺序执行
document.body.appendChild(script);
加载时序对比
普通 script:
HTML: ──────█████████████──────────────────────▶
↑停止解析 ↑继续解析
JS: ████下载████████执行████
defer:
HTML: ──────────────────────────────────────────▶
JS: ████下载████ ████执行████
↑HTML解析完成后
async:
HTML: ──────────────────█████──────────────────▶
↑暂停 ↑继续
JS: ████下载████████执行████
↑下载完立即执行
使用场景
<!-- 依赖 DOM 的脚本,需要保证顺序 -->
<script src="jquery.js" defer></script>
<script src="app.js" defer></script>
<!-- 独立的第三方脚本,如统计、广告 -->
<script src="analytics.js" async></script>
<!-- 按需加载 -->
<script>
button.onclick = () => {
loadScript('heavy-feature.js');
};
</script>
关键点
defer:并行下载,HTML 解析后按顺序执行,适合有依赖关系的脚本async:并行下载,下载完立即执行,顺序不确定,适合独立脚本- 动态创建 script 默认是 async 行为,设置
async = false可按顺序执行 defer和async仅对外部脚本有效,内联脚本会忽略这两个属性- 现代项目通常把
<script>放在</body>前,或使用defer
目录