defer脚本在html解析完成、domcontentloaded触发前按书写顺序执行,可安全操作dom;async脚本下载完立即中断解析执行,无序且可能访问不到未解析的dom元素。

defer 和 async 都能让 <script></script> 不阻塞 HTML 解析,但执行时机、顺序保障和适用场景完全不同——选错一个,document.querySelector('#app') 就可能返回 null。
defer 脚本一定等 DOM 就绪后才执行
浏览器遇到 <script defer src="app.js"></script> 时,会立即发起下载,但把执行“锁死”在两个节点之间:HTML 解析完成之后、DOMContentLoaded 触发之前。这意味着:
- 所有
defer脚本都能安全访问document.body、document.head和任意已声明的 DOM 元素 - 多个
defer脚本严格按 HTML 中的书写顺序执行(vue.js在app.js前面,就一定先执行) - 即使某个脚本下载慢,也不会拖慢其他
defer脚本的执行时机——它们统一等到解析结束再批量跑 -
defer对内联脚本无效:<script defer>init();</script>里的init()会立刻运行,属性被忽略
async 脚本下载完就中断解析执行
<script async src="analytics.js"></script> 的行为是“能多快就多快”:
- 下载并行、不阻塞解析,但一旦下载完成,浏览器会立刻暂停 HTML 解析,执行该脚本
- 执行可能发生在
还没开始解析、甚至都没闭合的时候 - 多个
async脚本谁先下载完谁先执行,完全无序;ads.js比analytics.js小,就可能先跑 - 如果脚本里调用
document.getElementById('main'),而对应元素还没解析到,结果就是null
async 和 defer 不能共存,也不支持内联脚本
浏览器对同时带 async 和 defer 的脚本只认 async 行为,另一个被忽略。更关键的是:
- 这两个属性只对带
src的外部脚本生效;<script defer>console.log(1)</script>中的defer完全无效 - 服务端渲染(SSR)中,常有人把初始化逻辑写在
的同步脚本里,误以为加个defer就能延迟——其实它根本没作用,得换成外部引用 - 需要动态控制加载时机(比如用户点击后才加载地图 SDK),还是用
document.createElement('script')更可靠,不受 HTML 解析阶段限制
现代工程中怎么选:看依赖,不看大小
别被“脚本体积大就该 async”这种经验误导。真正决定性因素是依赖关系:
- 用
defer:Vue/React 初始化、表单验证、埋点上报(要确保页面结构稳定)、jQuery 插件(必须等 jQuery 先加载) - 用
async:Google Analytics、百度统计、广告 SDK、独立工具函数库(如lodash-es某个工具方法) - Webpack 用户注意:
output.scriptLoading: 'defer'是安全默认值;若手动注入第三方async脚本,务必确认它不读取或操作 DOM
最容易被忽略的一点:defer 脚本会推迟 DOMContentLoaded 事件——哪怕脚本本身很小,只要它还没执行完,这个事件就不会触发。这对监听首屏就绪的逻辑有直接影响。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











