document.getelementbyid 返回 null 的根本原因是脚本执行过早,dom 尚未解析;应使用 defer(操作 dom 或有依赖时)、async(完全独立脚本)或 type="module"(自带 defer 但约束多),动态 import() 用于按需加载。

为什么 document.getElementById 总是返回 null
不是脚本没加载完,是它执行得太早——默认 <script src="app.js"></script> 遇到就暂停 HTML 解析,但若放在 里, 根本还没开始解析,DOM 树空空如也。
常见现象:Vue 挂载失败、按钮绑定无效、document.querySelector('#app') 报错。
根本原因不是网络慢,而是执行时机和 DOM 就绪状态不匹配。
defer 和 async 到底该选哪个
看脚本要不要碰 DOM、有没有依赖关系:
• defer:适合操作 DOM 或有顺序依赖的脚本,比如 vue.js 后接 main.js,浏览器保证按书写顺序执行,且一定在 DOMContentLoaded 前完成
• async:只适合完全独立的脚本,例如 analytics.js、error-reporter.js,它们只发请求、不读写 DOM、也不被其他脚本调用
• 混用风险极高:<script defer src="jquery.js"></script> 和 <script async src="plugin.js"></script> 并存时,plugin.js 可能抢在 jquery.js 前执行,直接报 ReferenceError: $ is not defined
type="module" 不是“更高级的 defer”,而是另一套规则
type="module" 脚本默认自带 defer 行为,但它还带来额外约束:
• 必须是外部文件(src 必填),内联 <script type="module">console.log(1)</script> 无法 import 其他模块
• 所有 import 路径必须是相对或绝对 URL,不支持 //cdn.example.com/a.js 这类协议无关路径
• 跨域模块必须带 crossorigin,否则静默失败、无错误提示
• 多个 type="module" 脚本仍按 HTML 中顺序执行,但和 defer 脚本不互通——不能指望 defer 脚本给模块提供全局变量
• IE 完全不识别 type="module",需配对使用 <script nomodule src="fallback.js"></script>
动态 import() 才是真正的按需加载
调整 <script></script> 位置或加 defer 只解决初始加载路径,没法响应用户行为:
• 点击“图表”按钮才加载 chart.js?得用 import('./chart.js')
• 评论区滚动到底部才拉取 SDK?得监听 IntersectionObserver 后触发 import()
• 注意:import() 返回 Promise,错误必须显式 .catch(),否则静默失败
• 动态创建的 type="module" 脚本(document.createElement('script'))不参与静态模块执行队列,执行时机不可控,别用来模拟依赖顺序
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











