
script 标签放哪最容易出错
直接写在 底部最稳妥。浏览器是顺序解析 HTML 的,如果 <script></script> 放在 里又没加 defer 或 async,脚本会阻塞页面渲染,还可能因为 DOM 元素还没加载完就执行 document.getElementById 之类操作,报 Cannot read property 'xxx' of null。
- 想操作 DOM?优先把
<script></script>放在前面 - 必须放
?加上defer属性(<script defer src="a.js"></script>),它会等 HTML 解析完再执行,且保持顺序 - 纯逻辑、不依赖 DOM 的脚本(比如埋点、工具函数)可加
async,但多个 async 脚本执行顺序不确定 - 用
DOMContentLoaded事件兜底也行,但别为了“保险”全包一层——多数时候是过度设计
内联 script 和外链 script 的实际区别
内联(<script>console.log('hi')</script>)适合极小段初始化逻辑,比如传入服务端渲染的配置;外链(<script src="main.js"></script>)才是主流,利于缓存、压缩和复用。但很多人忽略一点:内联脚本无法被浏览器缓存,每次 HTML 变了就得重新下载整个 script 块。
- 内联脚本里不能有
字符串,否则会被提前截断——要用或拆成字符串拼接 - 外链 JS 如果路径写错,控制台报
Failed to load resource: net::ERR_ABORTED,但页面不会崩溃,容易被忽视 - Vite/Webpack 等构建工具默认生成带哈希的文件名(如
main.a1b2c3.js),这时必须配合 HTML 注入插件,不能手写src
module 类型 script 的坑比想象中多
加了 type="module" 的 <script></script> 默认启用严格模式、支持 import/export,但它引入的是 ESM 规范,和传统 script 不兼容。最常踩的坑是:模块脚本自动启用 defer 行为,且不支持 document.write,更关键的是——它要求跨域请求带 CORS 头。
- 本地双击打开 HTML 文件(
file://协议)时,type="module"会直接报Cross origin requests are only supported for protocol schemes - 想在模块里用相对路径 import,路径必须带扩展名(
import { foo } from './utils.js'),不能省略.js - 模块脚本里的顶层
this是undefined,不是window—— 别指望靠this.foo = bar挂全局变量
eval 和 innerHTML 插入 script 几乎总是错的
用 eval() 执行字符串 JS 或通过 innerHTML += '<script>...</script>' 动态注入,看起来灵活,实则绕过所有安全机制和调试支持。现代浏览器会忽略通过 innerHTML 插入的 <script></script> 标签内容(不执行),而 eval 会污染作用域、阻碍 JS 引擎优化,还让 CSP 策略失效。
立即学习“Java免费学习笔记(深入)”;
- 需要动态加载?用
import()(动态 import)或document.createElement('script')+append()手动插入 - 后端返回带 script 的 HTML 片段?先剥离 script 标签,再用
textContent渲染内容,逻辑单独走 JS 加载流程 - 某些老 CMS 或编辑器强制输出含 script 的 HTML?加 CSP 的
unsafe-eval是下策,不如改输出结构
真正麻烦的从来不是“怎么加”,而是加完之后脚本执行时机、作用域边界、错误捕获位置这三件事没对齐。尤其在混合使用模块/非模块、内联/外链、同步/异步脚本时,一个 ReferenceError 可能来自加载顺序,也可能来自作用域隔离,得看 console 里报错堆栈的第一行在哪——而不是急着改 <script></script> 的位置。











