document.queryselector返回null主因是async脚本执行过早,dom未就绪;应改用defer(保障外链脚本按序在dom解析后执行)或domcontentloaded监听器,且defer对内联脚本无效。

脚本执行时报 document.querySelector 返回 null,八成是 async/defer 用反了——不是“加了就快”,而是“加错就崩”。
为什么 document.getElementById('app') 在 async 脚本里总返回 null
因为 async 脚本下载完就中断 HTML 解析立即执行,此时 DOM 树大概率没建完。<div id="app"> 可能还在后面几屏的 HTML 里,浏览器根本没解析到。这不是网络慢的问题,是执行时机本质错误。
<ul>
<li>
<code>async 执行可能发生在 尚未闭合时,document.body 都是 null
开始前就跑完async 脚本之间无序,a.js 依赖 b.js 导出的函数?大概率报 ReferenceError
defer,或把 DOM 操作包进 DOMContentLoaded 监听器里(但不如直接换属性干净)
defer 真的能保证 vue.js 在 app.js 前执行吗
能,而且不看文件大小、不看网络快慢,只看 HTML 中的书写顺序。浏览器把所有 defer 脚本放进一个队列,统一等到 HTML 解析完成、DOMContentLoaded 触发前按序执行。
-
<script src="vue.js" defer></script>必须写在<script src="app.js" defer></script>前面,app.js才能安全调用Vue.createApp - 这个顺序保障是硬性的,和构建工具无关;Webpack/Vite 输出的普通
<script src></script>仍需显式加defer - 但注意:
defer对内联脚本无效,<script defer>init();</script>中的init()会立刻执行,属性被忽略 - 如果构建产物用了
type="module",再手动加defer是冗余的——模块脚本默认具defer语义
async 和 defer 能不能一起写
不能。写成 <script async defer src="a.js"></script> 是合法 HTML,但浏览器只认 async,defer 被静默忽略。这不是 bug,是规范明确的行为。
- 规范规定:当两者共存时,
async优先级更高 - 实际效果等同于只写了
async,DOM 访问风险照旧 - 现代框架 CLI(如 Vite)生成的 HTML 一般不会这么写,但手写或 SSR 模板里容易误加
- 更隐蔽的坑:某些 CMS 或监控 SDK 的注入逻辑会自动补
async,覆盖你原本写的defer
SSR 页面里加 defer 就万事大吉了吗
不一定。服务端渲染中,常有人把首屏初始化逻辑写在 的内联脚本里,然后加个 defer 想“延迟执行”——这完全无效。
-
defer只对带src的外部脚本生效;<script defer>hydrate();</script>中的hydrate()仍会在 HTML 解析中途立刻运行 - 真正需要延迟的初始化逻辑,必须拆成外部文件引用,再加
defer - 动态加载(比如用户点击后才加载地图 SDK)别依赖这两个属性,用
document.createElement('script')更可控 - 还有个边界容易被忽略:
defer只管外链脚本本身的执行时机,不管它内部的import()动态导入——那些模块的加载和执行仍需代码自己兜底











