js在head中拿不到dom元素,因html自上而下解析,script执行时body尚未解析,dom节点不存在;解决方式有移至/body前、加defer属性或用domcontentloaded事件。

script 标签写在 head 里为什么 JS 拿不到 DOM 元素
因为 HTML 是自上而下解析的, 中的 <script></script> 会立即执行,此时 还没开始解析,所有 DOM 节点都不存在。
常见错误现象:document.getElementById('xxx') 返回 null,或调用 .addEventListener 报错 Cannot read property 'addEventListener' of null。
解决思路不是“等一会儿”,而是明确控制执行时机:
- 把
<script></script>移到闭合前(最简单有效) - 保留
中的<script></script>,但加上defer属性(仅适用于外部脚本) - 用
DOMContentLoaded事件包裹操作 DOM 的逻辑(适合内联脚本或需精细控制时)
引入外部 JS 文件:src 属性必须是绝对路径还是相对路径
src 支持相对路径、绝对路径、协议相对路径、甚至 data URL,没有强制要求必须是绝对路径。
关键在于路径是否能被浏览器正确解析为有效资源地址。常见问题:
- 相对路径以当前 HTML 文件所在目录为基准,比如
index.html在/pages/下,写src="js/main.js"就会请求/pages/js/main.js - 以
/开头是根相对路径,如src="/assets/script.js"→ 请求https://example.com/assets/script.js - 省略协议的写法
src="//cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"会自动继承当前页面协议(HTTP/HTTPS),但现代项目中更推荐显式写https: - 开发时用相对路径没问题;上线后若部署子路径(如
https://a.com/myapp/),需确认构建工具是否自动修正了src路径,否则 404
内联 script 和外部 script 能否混用变量
可以,但要注意执行顺序和作用域。
JavaScript 是按 <script></script> 标签出现顺序同步执行的(除非加 async 或 defer),所以变量声明和使用必须满足先后关系。
- 两个
<script></script>都是内联,且按顺序写:<script>let a = 1;</script>后面接<script>console.log(a);</script>—— 正常输出1 - 第一个是外链
<script src="a.js"></script>,第二个是内联<script>console.log(window.a);</script>—— 只有当a.js已加载并执行完,且把a挂到了window上,才不会报错 - 如果外链脚本加了
async,它可能比后面的内联脚本先执行,也可能后执行,顺序不确定,不能依赖这种混用方式传值 - 模块化场景(
type="module")下,内联模块和外链模块默认是独立作用域,不共享顶层变量,需显式export/import
async 和 defer 到底该选哪个
二者都用于优化页面加载性能,但行为差异明显,选错会导致脚本执行时机失控。
-
async:下载不阻塞 HTML 解析,但一旦下载完成就**立即执行**,执行时机不可预测;适合完全独立、无依赖的脚本(如统计代码、广告 SDK) -
defer:下载不阻塞 HTML 解析,执行则等到整个 HTML 解析完成、DOM 构建完毕后,**按书写顺序依次执行**;适合需要操作 DOM 且有依赖顺序的外链脚本(如 jQuery + 插件) - 两者都不能用在内联脚本上(
<script>...</script>不支持这两个属性) - 多个
defer脚本,即使网络加载有快慢,也会严格按 HTML 中出现顺序执行;async则谁先载完谁先执行,顺序无法保证 - 现代建议:优先用
defer;只有确认脚本绝对无 DOM 依赖、也无其他脚本依赖时,才考虑async
defer 只对**外链脚本**生效,而且很多人误以为加了 defer 就不用管 DOM 就绪逻辑了——其实它只保证执行在 DOM 构建之后,不保证 CSSOM 就绪或图片加载完成。如果脚本里要读取某个元素的 offsetHeight,仍可能拿到 0,得结合 requestAnimationFrame 或 load 事件进一步判断。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











