JavaScript脚本加载的两种方式:defer/async 的区别

deferasync是控制JavaScript脚本加载的两种方式。


传统脚本会阻塞HTML解析。


defer在后台下载脚本并在DOM解析完成后按顺序执行,适合依赖DOM或有依赖关系的脚本。


async下载完成后立即执行,但不保证顺序,适合独立脚本。


defer保证执行顺序且不阻塞解析,应在大多数情况下优先使用,而async适用于无依赖的第三方脚本。


正确使用这些属性可优化页面加载性能。


deferasync<script> 标签的两个属性,用于控制 JavaScript 脚本的加载时机执行顺序


它们解决了传统脚本加载会阻塞 HTML 解析的问题。


1. 传统脚本(无属性)

html 复制代码
<script src="script.js"></script>

行为

  1. 浏览器解析 HTML,遇到 <script> 标签

  2. 暂停 HTML 解析

  3. 下载脚本(阻塞)

  4. 执行脚本(阻塞)

  5. 恢复 HTML 解析


问题:如果脚本很大、网络慢,用户会看到白屏,页面长时间无响应。


2. defer(延迟执行)

html 复制代码
<script src="script.js" defer></script>

行为

  1. 浏览器遇到 <script defer>不阻塞 HTML 解析,在后台并行下载脚本

  2. HTML 解析完成后(DOMContentLoaded 事件触发前),按照脚本在文档中的顺序依次执行

  3. 执行完所有 defer 脚本后,触发 DOMContentLoaded 事件

特点

  • ✅ 不阻塞 HTML 解析

  • 保证执行顺序(按出现顺序)

  • ✅ 在 DOMContentLoaded 之前执行

  • ✅ 适合需要操作 DOM、且脚本之间有依赖关系的场景


3. async(异步执行)

html 复制代码
<script src="script.js" async></script>

行为

  1. 浏览器遇到 <script async>不阻塞 HTML 解析,在后台并行下载脚本

  2. 脚本下载完成后立即执行(此时可能 HTML 还未解析完)

  3. 执行时阻塞 HTML 解析(如果 HTML 还在解析中)

特点

  • ✅ 不阻塞 HTML 解析(下载阶段)

  • ⚠️ 不保证执行顺序(谁先下载完谁先执行)

  • ⚠️ 执行时机不确定,可能在 DOMContentLoaded 之前或之后

  • ✅ 适合独立的、无依赖关系的脚本(如埋点统计、独立组件)


4. 对比总结

特性 无属性 defer async
下载时机 遇到时立即下载 遇到时后台下载 遇到时后台下载
是否阻塞 HTML 解析 ✅ 阻塞 ❌ 不阻塞(下载阶段) ✅ 阻塞(执行阶段) ❌ 不阻塞(下载阶段) ✅ 阻塞(执行阶段)
执行时机 下载后立即执行 HTML 解析完成后 下载完成后立即执行
执行顺序 按出现顺序 ✅ 按出现顺序 ❌ 不保证顺序
DOMContentLoaded 等待脚本执行完成 等待所有 defer 脚本执行完成 async 无关,可能早于或晚于

5. 可视化执行流程

html 复制代码
无属性:
HTML 解析: [=======] [暂停] [=======]
脚本下载:          [下载]
脚本执行:               [执行]

defer:
HTML 解析: [========================]
脚本下载:  [下载1] [下载2] [下载3]
脚本执行:                      [执行1][执行2][执行3] → DOMContentLoaded

async:
HTML 解析: [==========] [暂停] [=======] [暂停] [===]
脚本下载:  [下载1]      [下载2]      [下载3]
脚本执行:        [执行1]      [执行2]      [执行3]

6. 实际使用场景

场景 1:使用 defer

  • 需要操作 DOM 的脚本

  • 脚本之间有依赖关系(如 jQuery 在前,其他插件在后)

  • 多个脚本需要按特定顺序执行

html 复制代码
<script src="jquery.js" defer></script>
<script src="plugin.js" defer></script>   <!-- 依赖 jQuery,必须在其后执行 -->
<script src="app.js" defer></script>      <!-- 依赖两者 -->

场景 2:使用 async

  • 完全独立的脚本,不依赖其他脚本,也不依赖 DOM

  • 第三方统计代码(如 Google Analytics)

  • 广告脚本、社交插件

html 复制代码
<script src="https://www.googletagmanager.com/gtag.js" async></script>
<script src="ad-provider.js" async></script>   <!-- 两个脚本互不依赖 -->

场景 3:内联脚本

deferasync 只对外部脚本(有 src 属性)生效,对内联脚本无效:

html 复制代码
<!-- 无效,会被忽略 -->
<script defer>
  console.log('内联脚本不能 defer');
</script>

7. 常见误区

误区 1:defer 不会阻塞 DOMContentLoaded

✅ 正确。但注意:defer 脚本会在 DOMContentLoaded 之前 执行,所以 DOMContentLoaded 会等待所有 defer 脚本执行完毕。

误区 2:async 一定在 DOMContentLoaded 之前执行

❌ 错误。如果脚本下载很快,可能在 HTML 解析完成前就执行;如果下载慢,可能在 DOMContentLoaded 之后执行。

误区 3:deferasync 都能提升性能

⚠️ 不一定。如果脚本有依赖关系,用错 async 可能导致执行顺序错乱、报错。选对属性才能提升性能。


8. 与 DOMContentLoadedload 的关系

javascript 复制代码
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM 解析完成,所有 defer 脚本已执行');
});

window.addEventListener('load', () => {
  console.log('所有资源(图片、样式、async 脚本等)加载完成');
});
脚本类型 与 DOMContentLoaded 的关系
无属性 阻塞 DOMContentLoaded
defer DOMContentLoaded 等待所有 defer 执行完成
async 与 DOMContentLoaded 无关,可能在前后都可能

9. 最佳实践建议

  1. 优先使用 defer :大多数业务脚本都适合 defer,既能并行下载,又能保证执行顺序

  2. 独立脚本用 async:埋点、广告等无依赖的第三方脚本

  3. 关键脚本可以放在 <head> 并用 defer:避免阻塞首屏渲染

  4. 避免在 <head> 中使用无属性脚本:会严重阻塞页面渲染

  5. 尽量将脚本放在 <body> 底部 :即使不加属性,也能减少阻塞影响(但不如 defer 优雅)


10. 快速决策表

你的需求 推荐方案
脚本需要操作 DOM defer
脚本之间有依赖关系 defer
脚本完全独立,不依赖任何东西 async
需要在 HTML 解析过程中立即执行 无属性,放在需要的位置
不确定选哪个 默认选 defer
相关推荐
哀木17 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧051317 小时前
ctf show web入门27
前端
小村儿17 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程
竹林81818 小时前
用ethers.js连接MetaMask实现Web3钱包登录:从踩坑到稳定运行的完整记录
前端·javascript
heyCHEEMS18 小时前
如何用 Recast 实现静态配置文件源码级读写
前端·node.js
心连欣18 小时前
从零开始,学习所有指令!
前端·javascript·vue.js
review4454318 小时前
大模型和function calling分别是如何工作的
前端
东东同学18 小时前
耗时一个月,我把 Nuxt 首屏性能排障经验做成了一个 AI Skill
前端·agent
工业甲酰苯胺18 小时前
Redis--集群搭建与主从复制原理
数据库·redis·php
冴羽19 小时前
超越 Vibe Coding —— AI 辅助编程指南
前端·ai编程·vibecoding