<script>标签对HTML文件解析过程的影响以及async和defer属性的应用
在前端开发中,<script>
标签的 async
和 defer
属性会显著影响 JavaScript 脚本的加载和执行时机。下面结合示例代码,详细解析它们之间的区别:
1. 默认情况(无 async
/defer
)
<script src="script.js"></script>
特性
- 阻塞渲染:浏览器遇到
<script>
标签后,会立即暂停 HTML 解析,下载并执行脚本,直到脚本执行完毕才继续解析 HTML。 - 执行顺序:按照标签在 HTML 中的出现顺序执行。
- 适用场景:需要立即执行的脚本,或依赖于当前 DOM 的脚本。
示例
<!DOCTYPE html>
<html>
<head><title>默认脚本加载</title>
</head>
<body><div id="app">加载中...</div><!-- 阻塞渲染,直到 script.js 下载并执行完毕 --><script src="script.js"></script><div>其他内容</div>
</body>
</html>
2. async
属性
<script async src="script.js"></script>
特性
- 异步下载:脚本的下载与 HTML 解析同时进行,不会阻塞渲染。
- 立即执行:脚本下载完成后,会立即暂停 HTML 解析并执行脚本。
- 执行顺序不确定:多个
async
脚本的执行顺序取决于下载完成的时间,与标签顺序无关。 - 适用场景:独立的、不依赖于其他脚本或 DOM 的异步脚本(如广告、分析工具)。
示例
<!DOCTYPE html>
<html>
<head><title>async 脚本加载</title>
</head>
<body><div id="app">加载中...</div><!-- 不阻塞渲染,下载完成后立即执行 --><script async src="analytics.js"></script><div>其他内容</div>
</body>
</html>
3. defer
属性
<script defer src="script.js"></script>
特性
- 异步下载:脚本的下载与 HTML 解析同时进行,不会阻塞渲染。
- 延迟执行:脚本会在 HTML 解析完成后、
DOMContentLoaded
事件触发前执行。 - 执行顺序确定:多个
defer
脚本按标签在 HTML 中的出现顺序执行。 - 适用场景:需要操作 DOM 或依赖于其他脚本的库(如 jQuery)。
示例
<!DOCTYPE html>
<html>
<head><title>defer 脚本加载</title><!-- defer 脚本会在 HTML 解析完成后按顺序执行 --><script defer src="jquery.js"></script><script defer src="app.js"></script>
</head>
<body><div id="app">内容</div>
</body>
</html>
ps:
在浏览器解析 HTML 的过程中,defer
脚本的执行先于 DOMContentLoaded
事件。
执行顺序原理
-
defer
脚本的特性:- 带有
defer
属性的脚本会与 HTML 解析并行下载(不阻塞解析)。 - 下载完成后不会立即执行,而是等待整个 HTML 文档解析完成(即 DOM 构建完成)后,再按脚本在 HTML 中的顺序依次执行。
- 带有
-
DOMContentLoaded
事件的触发时机:- 当整个 HTML 文档解析完成(DOM 树构建完毕),且所有
defer
脚本执行完成后,才会触发DOMContentLoaded
事件。
- 当整个 HTML 文档解析完成(DOM 树构建完毕),且所有
示例验证
<!DOCTYPE html>
<html>
<head><!-- defer 脚本:在 HTML 解析完成后执行 --><script defer src="defer-script.js"></script><script>// 监听 DOMContentLoaded 事件document.addEventListener('DOMContentLoaded', () => {console.log('DOMContentLoaded 事件触发');});</script>
</head>
<body><h1>测试执行顺序</h1>
</body>
</html>
// defer-script.js
console.log('defer 脚本执行');
输出顺序:
defer 脚本执行
DOMContentLoaded 事件触发
结论
defer
脚本的执行先于 DOMContentLoaded
事件。
defer
的设计初衷就是保证脚本在 DOM 就绪后、DOMContentLoaded
之前执行,适合需要操作 DOM 且依赖执行顺序的场景(如加载 jQuery 后再执行依赖它的代码)。
执行时机对比图
HTML 解析 |---------------------------->|
默认脚本 | 下载并执行 |
async 脚本 | 下载 | 执行 |
defer 脚本 | 下载 | | 执行
DOMContentLoaded |---------------------------->|
关键区别总结
特性 | 默认(无属性) | async | defer |
---|---|---|---|
是否阻塞渲染 | 是 | 否 | 否 |
下载时机 | HTML 解析暂停 | 与 HTML 解析并行 | 与 HTML 解析并行 |
执行时机 | 下载后立即执行 | 下载完成后立即执行 | HTML 解析完成后执行 |
执行顺序 | 按标签顺序 | 不确定(下载完成顺序) | 按标签顺序 |
DOM 依赖 | 需确保 DOM 已加载 | 不依赖 DOM | 可依赖 DOM |
最佳实践
- 优先使用
defer
:对于需要操作 DOM 或依赖于其他脚本的代码,使用defer
。 - 使用
async
加载独立脚本:对于不依赖其他资源的异步脚本(如第三方库),使用async
。 - 内联脚本避免使用
async
/defer
:async
和defer
仅对外部脚本(src
属性)有效,对内联脚本无效。
兼容性
async
和defer
均支持现代浏览器及 IE10+。- 若需兼容 IE9 及以下版本,需使用默认加载方式或通过 JavaScript 动态加载脚本。