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
相关推荐
luanma1509802 小时前
PHP vs C#:30字秒懂两大语言核心差异
android·开发语言·python·php·laravel
天若有情6732 小时前
开篇必看:零基础吃透前端,别再盲目死记硬背了
前端
RulerMike2 小时前
three 实现简单机械臂逆运动
前端·ai编程·three.js
darkb1rd2 小时前
从“会聊天”到“会搭页面”:一次 TinyEngine + MCP 的前端智能化实战思路
前端
社恐的下水道蟑螂2 小时前
从奶茶店彻底搞懂 SSR!从零到拿捏服务端渲染,看完面试吹牛逼不卡壳
前端·react.js·性能优化
EnCi Zheng2 小时前
M1-如何转换为HTML
前端·html
fresh hacker2 小时前
【Linux系统】通用的“系统排障”
linux·运维·服务器·网络·php
进击的尘埃2 小时前
用了大半年 Claude Code,我总结了 12 个真正改变工作流的配置技巧
javascript
luanma1509802 小时前
Laravel 8.X重磅特性全解析
前端·javascript·vue.js·php·lua