async和defer仅对带src的外部脚本生效;内联脚本添加二者无效,仍立即执行并阻塞解析;async与defer共存时以async为准;多个defer脚本按序执行,async则无序。

async 和 defer 在没有 src 的 script 标签里完全无效
直接说结论:async 和 defer 属性只对带 src 的外部脚本生效。如果 <script>console.log(1)</script> 这种内联脚本加了这两个属性,浏览器会忽略它们,行为和没加一样——立即执行、阻塞 HTML 解析。
常见错误现象是:开发者在调试加载顺序时,给内联 <script></script> 加了 defer,以为它会推迟到 DOM 构建完再运行,结果发现还是提前报错(比如访问了还没解析的元素)。
- 内联脚本不支持
async/defer,这是 HTML 规范明确规定的 - Chrome/Firefox/Safari 均按规范处理,不会报错,但也不会改变执行时机
- 想延迟执行内联逻辑?必须手动包装成
DOMContentLoaded或setTimeout
async 与 defer 同时出现时,浏览器只认 async
HTML 解析器遇到 <script async defer src="a.js"></script>,会直接忽略 defer,按 async 行为处理:脚本下载不阻塞 HTML,但一旦下载完成就立刻执行,可能打断 DOM 构建。
这个细节容易被忽略,尤其在构建工具自动生成标签或多人协作改配置时。实际效果不是“先异步下载、再延迟执行”,而是彻底的异步执行。
-
async和defer语义冲突,规范要求以async为准 - 即使
async脚本体积很小、下载极快,它仍可能在中就执行,此时document.body可能还不存在 - 测试时可用
performance.getEntriesByType('resource')查看真实下载/执行时间点,别只靠console.log推断
多个 defer 脚本严格按书写顺序执行,但 async 不保证
这是最常被拿来控制依赖关系的点:defer 脚本不仅延迟执行,还保持 <script></script> 标签在 HTML 中的顺序;而 async 脚本谁先下完谁先跑,顺序不可控。
典型踩坑场景:用 async 加载 jQuery 和插件脚本,结果插件先执行、$ 还没定义就报 ReferenceError。
-
defer脚本会在DOMContentLoaded前按序执行,适合有依赖的模块链(如utils.js→main.js) -
async适合完全独立、无依赖的脚本(如统计 SDK、广告代码) - 注意:所有
defer脚本都会等到整个 HTML 解析完成才开始执行,哪怕某个脚本下载慢,也会拖慢后续所有defer脚本的执行起点
老旧浏览器(IE9–IE10)对 defer 的支持有隐藏陷阱
IE9–IE10 确实支持 defer,但它有个致命限制:只对 <script></script> 标签出现在 中且未指定 src 的情况做特殊处理——等等,不对,这其实是误解。真实问题是:IE9–IE10 要求 defer 脚本必须在 内声明,如果写在 靠后位置,它会退化为同步执行。
更隐蔽的是:IE9 对 defer 的判断基于标签是否“静态存在于初始 HTML”,动态插入的 <script defer></script> 完全不触发延迟行为。
- 在 IE9–IE10 中,把
defer脚本放底部 ≠ 安全,它可能立即执行 - 兼容方案不是禁用
defer,而是统一放在,并确保是静态 HTML(非 JS 动态创建) - 如果必须支持 IE9,建议用
document.write注入(虽然不推荐)或降级为监听readystatechange
src 属性弄丢了,导致 async 彻底失效。测加载顺序,别只看控制台日志,得抓 network timing + DOM ready 时间戳交叉验证。











