脚本操作dom必须用defer:defer确保dom解析完成后再执行,保证元素可用且顺序执行;async适用于无dom依赖的脚本;两者不可共存,同步脚本是性能雷区。

脚本要操作 DOM?必须用 defer
如果脚本里写了 document.getElementById("app")、document.querySelector(".header") 或 addEventListener,它就必须等 DOM 解析完才能跑——否则大概率拿到 null,直接报错。这不是“运气不好”,而是 async 的执行时机根本无法保证元素存在。
-
defer脚本一定在 HTML 解析完成、DOMContentLoaded触发前执行,此时document.body和所有已声明的元素都可用 - 多个
defer脚本严格按 HTML 中的书写顺序执行,适合「框架 + 业务逻辑」组合,比如:<script src="vue.js" defer></script>后紧跟<script src="main.js" defer></script> -
defer对内联脚本无效:<script defer>init();</script>中的init()会立刻执行,属性被浏览器静默忽略 - Webpack/Vite 构建产物若已是
type="module",再加defer实际无意义——模块脚本默认就具defer语义
脚本只上报、不读 DOM、也不被依赖?优先用 async
典型如埋点统计、广告 SDK、A/B 测试脚本:里面只有 navigator.sendBeacon、fetch 或第三方 track() 调用,没任何 document. 操作,也不依赖 jQuery 或其他库——这种脚本用 async 才合理。
-
async脚本一下载完就中断 HTML 解析执行,可能发生在标签都还没解析到的时候,所以不能假设任何 DOM 元素存在 - 多个
async脚本谁先下完谁先跑,analytics.js和ads.js之间毫无顺序保障 -
async和defer不能共存,浏览器只认async行为,另一个被忽略 - 主业务脚本(如
main.js)设成async是高危操作:体积大 + 执行慢 = 中断解析拖慢首屏,用户可能只看到标题就卡住
不加属性的 <script src="..."></script> 是性能雷区
同步加载会让浏览器暂停 HTML 解析,等脚本下载 + 执行完才继续。网络稍慢,首屏就是白屏——尤其放在 里的同步脚本,在 SSR 场景下极易卡住渲染。
- 除非是极小的内联 polyfill(如检测
Promise是否存在),否则所有外部脚本都应明确指定defer或async -
defer和async都只对带src的外部脚本生效;没src的标签加了也白加 - 动态创建 script 标签(
document.createElement('script'))更适合按需加载场景,比如点击后才加载地图 SDK,完全绕开 HTML 解析阶段限制
容易被忽略的边界情况
defer 只锁死外链脚本本身的执行时机,不管它内部行为。即使加了 defer,如果脚本里用了 import() 动态导入,后续模块的加载和执行仍不受约束,得靠代码自己兜底。
-
document.write在defer/async脚本中会直接报错,必须禁用 - 监听脚本加载完成要用
script.onload,不是DOMContentLoaded——后者是整个 HTML 解析完的事件,而onload才对应单个脚本加载完毕 - 服务端渲染时,有人把初始化逻辑写在
的同步脚本里,以为加个defer就能延迟——其实它根本没作用,必须换成外部引用才能生效
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











