defer脚本在html解析完成、dom构建完毕后且domcontentloaded事件触发前执行,可安全访问document.body等全部dom元素,严格按html中书写顺序执行,仅对外部脚本(含src)生效。

defer脚本什么时候执行?DOM就绪后、DOMContentLoaded前
defer脚本一定在 HTML 解析完成、DOMContentLoaded 触发前执行,此时 document.body、document.head 和所有已声明的 DOM 元素都可用。这不是“大概率”,而是浏览器强制保证的时序。
- 多个
defer脚本严格按 HTML 中书写顺序执行:哪怕b.js比a.js下载快,也必须等a.js执行完才轮到b.js -
defer只对带src的外部脚本生效;<script defer>init()</script>中的defer被忽略,init()会立刻运行 - Webpack/Vite 构建产物若已用
type="module",再加defer实际无效——模块脚本默认具defer语义
async脚本为什么常导致document.getElementById返回null?
因为 async 脚本一下载完就中断 HTML 解析去执行,可能发生在 标签都还没解析到的时候。此时调用 document.getElementById("app") 或 document.querySelector("#header") 返回 null 是常态,不是 bug,是行为预期。
- 典型错误现象:
TypeError: Cannot read property 'addEventListener' of null→ 脚本用了async却尝试绑定 DOM 元素 - 多个
async脚本执行顺序完全不可控:统计脚本analytics.js和广告脚本ad-banner.js谁先执行,只取决于谁先下完 -
async脚本内部写window.addEventListener('DOMContentLoaded', ...)多余——它自己可能早于或晚于该事件,无法预测
async和defer能同时写吗?浏览器只认async
如果写成 <script async defer src="a.js"></script>,现代浏览器按 HTML 规范直接忽略 defer,按 async 行为处理。这不是兼容性问题,是明确规定的优先级:async > defer。
- 这个规则适用于所有支持两者的浏览器(IE10+、Chrome、Firefox、Safari),无需降级处理
- 别指望“两个都加更保险”——反而会让本该保序的业务脚本变成无序执行,埋下隐性故障
- 服务端渲染(SSR)页面中,有人把初始化逻辑塞进
里的同步脚本,再加defer,结果无效:因为没src,属性被忽略
不加任何属性的script标签有多危险?
默认行为是同步加载:遇到 <script src="x.js"></script>,HTML 解析立刻暂停,等 JS 下载、编译、执行完才继续。首屏白屏时间直接受网络延迟和脚本体积影响,Lighthouse 性能分暴跌。
- HTTP/1.1 下受队头阻塞影响明显;HTTP/2 虽缓解下载,但执行阻塞仍在
- 放在
里的同步脚本危害最大,尤其 SSR 场景下容易卡住首屏渲染 - 唯一可容忍的例外:极小的内联 polyfill(如检测
Promise是否存在),且必须确保不操作 DOM
真正容易被忽略的点是:defer 和 async 都只约束外链脚本的执行时机,对脚本内部的动态行为(比如 import() 加载、fetch 请求、定时器回调)毫无约束力。它们解决的是“脚本何时开始跑”,而不是“脚本里做的事是否安全”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











