defer 脚本在 HTML 解析完成后按序执行,可安全操作 DOM;async 脚本下载完立即中断解析执行,执行时机不可控且无序;选择依据是是否依赖完整 DOM 或脚本间顺序,而非文件大小。

长脚本加载时,defer 和 async 的行为差异会非常明显——不是“都异步”,而是“异步时机”和“执行时机”完全不同。关键不在下载快慢,而在浏览器如何调度执行。
defer:等页面解析完,再排队执行
即使脚本体积大、下载耗时长,带 defer 的脚本也不会打断 HTML 解析。浏览器一边继续构建 DOM,一边在后台下载脚本;等整个 HTML 解析完毕(DOMContentLoaded 触发前),才按顺序依次执行所有 defer 脚本。
- 执行一定发生在 DOM 完全就绪之后,可安全操作 任何元素
- 多个 defer 脚本严格按 HTML 中的书写顺序执行,哪怕 script2 比 script1 先下载完,也必须等 script1 执行完再轮到它
- 如果某个 defer 脚本卡住(比如内部有死循环或长时间同步计算),会延迟 DOMContentLoaded 触发,但不会影响页面视觉渲染进度
async:谁先下完谁先跑,不管 DOM 状态
async 脚本一旦下载完成,立刻中断当前 HTML 解析流程来执行它——哪怕此时 <body> 还没开始解析,甚至 <head> 都没扫完。
- 执行时机完全不可控:可能在 <html> 标签刚读完时就执行,也可能在 </body> 之后才执行
- 长脚本若下载慢,async 不会“等”,但短脚本若提前下完,就会抢在 DOM 构建中途插入执行,此时 document.body 可能还为 null
- 多个 async 脚本之间无执行顺序保障,scriptB 即使写在 scriptA 后面,也可能因体积小而先执行
对页面渲染的实际影响
两者都不阻塞下载阶段,但阻塞点不同:
- defer:只在 DOM 解析完成后集中阻塞一次(执行队列),且阻塞时间可控(所有脚本顺序执行)
- async:可能在解析过程中多次随机阻塞(每个脚本各自插队),导致渲染中断点分散、难以预测
- 长脚本配 async 时,若它恰好是第一个下载完的,会强制暂停解析、执行、再恢复——此时用户可能只看到 <title>,其余内容全白屏
怎么选:看脚本是否依赖完整 DOM 或其他脚本
不看体积大小,看逻辑关系:
- 需要操作 document.getElementById('app')?→ 用 defer
- 脚本之间有 require 关系(如 jQuery + 插件)?→ 必须 defer
- 只是上报 PV、加载广告位、埋点 SDK?→ async 更合适,早执行早收集
- 脚本本身含 document.write()?→ 两种都禁用,它会清空当前文档

















