在 HTML 中会遇到以下三类 script:
<script src='xxx'></script>
<script src='xxx' async></script>
<script src='xxx' defer></script>
那么这三类 script 有什么区别呢?
script
浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当代码执行完毕后恢复解析。整个过程如下图所示:
可以看到,script 阻塞了浏览器对 HTML 的解析,如果获取 JS 脚本的网络请求迟迟得不到响应,或者 JS 脚本执行时间过长,都会导致白屏,用户看不到页面内容。
async script
当HTML解析过程中遇到script async标签时,不会中断HTML解析,同时并行下载script文件,下载完成后中断HTML解析并执行script,执行完成后再继续HTML解析
script的执行顺序不一定按照script标签的出现顺序,而是取决于script下载完成的顺序
defer script
当HTML解析过程中遇到script defer标签时,不会中断HTML解析,同时并行下载script文件,直到HTML解析完成再执行script(script的执行顺序与script标签出现顺序一致)
async 与 defer 的区别
async
和 defer
是两种用于控制外部 JavaScript 脚本加载和执行行为的属性,它们都可以提高页面性能,但它们的工作方式和适用场景有所不同。下面是它们的详细区别:
1. 加载与执行顺序
async
:
-
加载:当浏览器解析 HTML 文档时,遇到带有
async
属性的<script>
标签,脚本会被异步下载,与 HTML 的解析同时进行。 - 执行:一旦脚本下载完成,浏览器会立即停止 HTML 解析,执行该脚本。这意味着脚本的执行顺序取决于下载完成的时间,无法保证按照 HTML 中的顺序执行。
defer
:
-
加载:和
async
类似,脚本在后台异步下载,与 HTML 文档解析同时进行。 -
执行:
defer
保证脚本会在 HTML 文档完全解析之后才执行,而且多个带defer
的脚本会按它们在 HTML 中的顺序执行。
2. 执行时机
async
:当脚本文件下载完成时立即执行,无论 HTML 文档是否解析完毕。这可能导致脚本在页面 DOM 完全构建之前执行,因此如果脚本需要操作 DOM,可能会遇到问题。defer
:保证在整个 HTML 文档解析完成后,才会执行脚本。适合需要操作 DOM 的脚本,因为这时整个文档结构已经构建完毕。
3. 适用场景
async
:
- 适合不依赖页面内容(DOM)的脚本,比如广告、数据分析、第三方服务(如 Google Analytics)。
- 因为脚本不依赖于页面的其他部分,快速加载和执行是关键,因此
async
属性可以最大限度地加快这些脚本的加载。
defer
:
- 适合依赖 DOM 结构的脚本,通常是需要在页面加载后进行操作的代码(如初始化 UI、添加事件监听等)。
- 在大型项目中,
defer
常用于需要确保所有 DOM 元素都已加载完成时再执行的脚本。
4. 脚本顺序控制
async
:脚本下载完就执行,因此多个脚本的执行顺序不可控,可能会出现某个脚本比其他脚本提前执行,即使它们在 HTML 中的位置较后。defer
:多个带有defer
的脚本按它们在 HTML 文档中的顺序执行,顺序性得到保证。
5. 浏览器支持
-
async
和defer
都受到主流浏览器的广泛支持,包括现代桌面和移动浏览器。老版本的浏览器可能对defer
处理不如现代浏览器精确,但在绝大多数情况下,它们的行为是一致的。
总结比较:
特性 | async |
defer |
---|---|---|
加载方式 | 异步加载,与 HTML 解析同时进行 | 异步加载,与 HTML 解析同时进行 |
执行时机 | 脚本下载完成后立即执行,可能在 HTML 解析完毕前执行 | HTML 解析完毕后,按顺序执行 |
执行顺序 | 无法保证执行顺序 | 保证按 HTML 中的顺序执行 |
适用场景 | 不依赖 DOM、非关键脚本(如广告、分析、第三方 SDK) | 依赖 DOM 的脚本,通常是与页面结构相关的脚本 |
性能优化 | 加快脚本加载,但可能会中断 HTML 解析 | 不会中断 HTML 解析,推荐用于需要 DOM 完全加载的脚本 |
示例对比:
-
async
示例:<script src="analytics.js" async></script> <script src="ads.js" async></script>
- 这些脚本会并行下载,且各自下载完成后立即执行,可能会出现
ads.js
比analytics.js
先执行。
- 这些脚本会并行下载,且各自下载完成后立即执行,可能会出现
-
defer
示例:<script src="main.js" defer></script> <script src="ui.js" defer></script>
-
main.js
和ui.js
都会在 HTML 完全解析后按顺序执行,main.js
会先执行,接着是ui.js
。
-
何时使用哪个?
- 如果你需要确保脚本按顺序执行且依赖页面结构(如操作 DOM),使用
defer
是最佳选择。 - 如果脚本可以独立于页面内容并立即执行(如第三方追踪代码或广告),则使用
async
可以提高页面加载性能。
这两个属性都可以帮助优化网页的加载速度,关键在于选择合适的场景应用。
type="module"
type="module" 代表的是引入 JavaScript 模块的方式,模块(Modules)是一种更有组织性、更灵活的代码管理方式。随着 ES6(ECMAScript 2015)的引入,JavaScript 增加了对模块的原生支持,使得我们可以将代码分成独立的文件,并根据需要引入它们。
为了让浏览器正确识别 JavaScript 模块,我们需要在 <script>
标签中使用 type="module"
:
<script type="module" src="path/to/your-module.js"></script>
主要特性
模块作用域:模块内部的变量默认是私有的,只有通过
export
导出的内容才能在其他模块中访问。模块间不会共享全局作用域,避免了变量冲突。延迟执行(Deferred Execution):模块默认是延迟执行的,类似于使用了
defer
的<script>
标签。这意味着它们会在 HTML 文档完全解析后执行。严格模式(Strict Mode):模块始终在严格模式下运行(即使未显式声明),这有助于避免常见的 JavaScript 错误。
模块的异步加载:浏览器会异步加载模块,不会阻塞 HTML 文档的解析过程。