是的,defer脚本执行时DOM一定就绪,因其在HTML解析完成、DOMContentLoaded触发前按序执行,此时document.body、所有元素节点均已存在;而async脚本下载完立即执行,可能DOM未就绪。

defer 脚本执行时 DOM 一定就绪吗?
是的,defer 脚本执行时整个 DOM 树已解析完成,document.body、document.head 和所有元素节点都已存在。这是它和 async 最关键的区分点。
常见错误现象:document.getElementById("app") 返回 null、addEventListener 报错、Vue/React 初始化失败——八成是因为用了 async 或没加任何属性,而非 defer。
-
defer只对带src的外部脚本生效;写成<script defer>init();</script>会被浏览器忽略 - 多个
defer脚本严格按 HTML 中出现顺序执行,适合「库 + 业务逻辑」组合,比如先加载vue.js再加载app.js - 执行时机在 HTML 解析完成之后、
DOMContentLoaded触发之前,不阻塞渲染,也不插队执行
async 脚本为什么不能保证执行顺序?
因为 async 的语义是“谁先下载完,谁先执行”,完全不关心脚本在 HTML 中的位置。
使用场景很明确:脚本必须独立,既不操作 DOM,也不依赖其他脚本,比如 analytics.js、ads.js、error-tracking.js。
立即学习“前端免费学习笔记(深入)”;
- 多个
async脚本之间无执行顺序保障,A 在 B 前面声明,但 A 体积大、B 体积小,B 就可能先执行 -
async下载完会立即暂停 HTML 解析来执行,哪怕此时 DOM 还没构建完——这可能导致脚本内document.querySelector失败 - 别把主业务脚本(如
main.js)设为async:体积大 + 执行慢 = 插队中断解析,拖慢首屏
async 和 defer 能一起写吗?
不能。<script async defer src="a.js"></script> 是无效的。规范明确:两者共存时,async 优先级更高,defer 被静默忽略。
这不是浏览器 bug,是标准行为。你写了,浏览器就当没看见 defer。
- Webpack/Vite 构建产物若已用
type="module",再手动加defer实际无效——模块脚本默认具defer语义 -
defer只管外链脚本的加载与执行时机,不管它内部的动态行为。即使加了defer,如果脚本里用了import(),后续模块仍需自行控制加载节奏
不加属性的 script 为什么是性能雷区?
<script src="x.js"></script> 默认同步加载:HTML 解析立刻暂停,等 JS 下载、编译、执行完才继续。网络慢时白屏明显,首屏时间直接拉长。
除非是极小的内联 polyfill(比如检测 Promise 是否存在),否则所有外部脚本都应明确指定 defer 或 async。
- 放在
<head>里的同步脚本危害最大,尤其 SSR 场景下容易让首屏渲染卡住 - 很多人误以为把脚本挪到
<body>底部就能解决,但现代构建工具生成的 bundle 往往无法手动调整位置,defer是更可控、更可维护的方案
defer 保证的是脚本执行时 DOM 就绪,但不保证它内部异步加载的资源(比如动态 import()、fetch 请求)也已完成**。这点常导致「DOM 存在但数据还没回来」的竞态问题,得靠代码自己兜底处理。



















