
一、基础知识
在JavaScript中,defer与async属性均用于优化外部脚本的加载机制,避免传统同步加载阻塞HTML文档解析,提升页面性能。尽管二者目标一致,但在执行逻辑、脚本加载顺序、执行时机以及对DOM构建的影响等方面存在本质差异。下面将逐一详细解析。
1.1 加载顺序
defer:脚本在文档解析过程中异步下载,但执行顺序严格遵循其在HTML文档中出现的顺序。多个带有defer属性的脚本会按序执行,确保依赖关系的正确性。
async:脚本同样异步下载,但一旦下载完成即立即执行,不等待文档解析完成,也不保证多个脚本之间的执行顺序。
1.2 执行时机
defer:脚本在文档解析完成后、DOMContentLoaded事件触发前执行,属于“文档就绪后执行”的典型模式。
async:脚本在下载完成后立即执行,可能在文档解析过程中或完成前执行,执行时机不可控。
1.3 使用场景
defer:适用于需要等待整个文档结构构建完成才能运行的脚本,如依赖DOM操作的初始化代码、模块化应用的主入口文件等。
async:适用于独立性强、不依赖其他脚本或DOM结构的脚本,如第三方统计代码、广告脚本、非关键功能模块等。
1.4 对DOM的影响
defer:由于在DOMContentLoaded前执行,此时DOM已完全构建,可安全访问和操作所有页面元素。
async:可能在DOM未完全解析时执行,若脚本中包含对DOM的操作,极易出现元素获取失败、事件绑定无效等问题。
二、属性对比
属性 | 无属性 | defer | async |
|---|---|---|---|
加载方式 | 同步加载,阻塞HTML解析 | 异步加载,不阻塞HTML解析 | 异步加载,不阻塞HTML解析 |
执行顺序 | 按在HTML中出现的顺序立即执行 | 在DOM解析完成后,按HTML顺序执行 | 下载完成后立即执行,不保证顺序 |
执行时机 | 立即执行,在DOMContentLoaded之前 | DOM解析完成后,DOMContentLoaded之前 | 下载完成后立即执行,时机不确定 |
适用场景 | 需要立即执行的小脚本 | 依赖DOM、需要按顺序执行的脚本 | 独立、不依赖其他脚本的功能 |
三、常见问题分析
多个关键业务脚本未使用defer或async,会导致页面解析被阻塞,首屏渲染时间延长。
部分依赖 DOM 的初始化脚本使用了async,存在执行时机过早、操作失败的风险。
如果多个defer脚本的引入顺序不合理,会影响模块依赖关系。
第三方统计脚本未异步加载,拖慢整体性能。
四、优化方案
4.1 分类管理脚本资源
将所有外部JavaScript脚本按功能与依赖关系分为三类:
核心业务脚本:涉及DOM操作、用户交互逻辑,需保证执行顺序与时机,使用defer。
独立功能脚本:如日志上报、性能监控等,不依赖DOM与其他脚本,使用async。
内联脚本:对性能影响小、代码量少的逻辑,保留在<script>标签内,避免网络请求开销。
4.2 监控与性能对比
利用Chrome DevTools的Lighthouse工具,对优化前后页面的性能指标进行对比,重点关注:
First Contentful Paint(FCP)。
DOMContentLoaded 时间。
页面整体加载时间。
总结
defer:适用于依赖DOM或需要保持执行顺序的脚本(如业务主逻辑、初始化代码)。
async:适用于独立、不依赖其他脚本的第三方代码(如分析统计、广告脚本)。
现代最佳实践:使用 <script type="module">,它默认具有defer行为。
对于关键渲染路径上的脚本,考虑内联或使用preload/prefetch资源提示。
彩蛋
本文在Cnb平台上全面展示了JavaScript中defer和async属性的示例代码,欢迎访问《JavaScript defer与async属性演示完整示例》以获取更多详细内容。