script应放</body>前以避免DOM未加载导致null错误;defer保证顺序执行,async不保证;路径、MIME类型、跨域问题常致JS失效;动态加载需检测全局变量或用import()。

直接用 <script src="xxx.js"></script> 就行,但位置、加载时机和执行顺序不控制好,JS 很容易报错或失效。
script 标签放哪儿?head 里还是 body 底部?
绝大多数情况下,<script> 要放在 </body> 前——不是“习惯”,是避免 DOM 元素还没加载完,JS 就去查 document.getElementById,结果拿到 null。
例外情况只有两个:
- 需要 JS 提前拦截页面行为(比如做首屏防抖、UA 检测)
- 用了 defer 或 async 属性(见下一条)
常见错误现象:Uncaught TypeError: Cannot read property 'xxx' of null,八成是 script 放 head 里又没加 defer,还直接操作了 DOM。
defer 和 async 到底该选哪个?
两者都让 script 不阻塞 HTML 解析,但执行时机不同:
立即学习“前端免费学习笔记(深入)”;
-
defer:脚本下载异步,执行等整个 HTML 解析完、DOM 构建完成后再按顺序执行(保证顺序,适合有依赖的多个 JS) -
async:下载和执行都完全异步,谁先载完谁先执行(不保证顺序,适合统计、埋点等独立脚本)
别乱加 async 到 jQuery 或 Vue 的入口文件上——它可能比 vue.js 自身还早执行,报 ReferenceError: Vue is not defined。
兼容性:两者在 IE10+ 都支持,但 IE9 及以下只认 defer(且只对 src 外链有效)。
为什么有时 script 加了 src 还是没反应?
不是语法错了,而是这几个硬性条件漏了一个:
- 路径写错:
src="./js/main.js"中的./js/main.js实际不存在,浏览器控制台会报404(注意看 Network 面板) - MIME 类型不对:服务器返回的 JS 文件 header 是
text/plain而不是application/javascript,现代浏览器直接拒绝执行(尤其本地双击打开 HTML 时常见) - 跨域限制:用
file://协议打开 HTML 时,Chrome 禁止加载本地srcJS(报CORS错误),必须起个本地 server(比如npx serve)
调试建议:打开浏览器开发者工具 → Network 标签页 → 找对应 JS 文件 → 看 Status 是否为 200,Type 是否为 script。
动态加载 script 怎么写才可靠?
手动创建 <script> 标签并 append 到 document,关键不是“加进去”,而是“怎么知道它加载完了”:
不要只监听 load 事件就认为能用了——如果 JS 内部依赖另一个库(比如先加载 lodash.js,再加载业务代码),load 触发不代表逻辑就绪。
更稳妥的做法:
- 确保被加载的 JS 暴露一个全局变量(如
window._lodashLoaded = true) - 用
setTimeout轮询检查该变量,或用MutationObserver监听 script 标签的onload后再触发回调 - 现代项目优先用
import()动态导入(ESM),它原生支持 Promise,错误可 catch
容易被忽略的一点:动态插入的 script 默认是 async 行为,即使没写属性——这意味着它不会按插入顺序执行,别指望靠 DOM 插入顺序来控制依赖。


















