一、基础知识

在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属性演示完整示例》以获取更多详细内容。