需配合defer/async避免阻塞,仅对外部脚本生效;路径须准确(推荐绝对路径),不可混写内联代码,onerror仅捕获加载失败而非执行错误。

script 标签的 src 属性到底怎么用
直接写 <script src="xxx.js"></script> 是最基础的加载方式,但它不等于“执行完再继续”,也不保证脚本就一定能用——浏览器默认是异步下载、同步执行(阻塞后续 HTML 解析),且一旦加载失败,onerror 不会自动触发,也不会抛出可捕获的 JS 错误。
常见错误现象:ReferenceError: xxx is not defined,但控制台没报 404;或者脚本明明在 Network 里显示 loaded,变量却访问不到。
- 确保路径正确:相对路径以当前 HTML 文件为基准,不是以 JS 文件位置为准;建议用以
/开头的绝对路径,比如src="/js/plugin.js" - 不要在
<script src="...">...</script>标签内部写 JS 代码,HTML 规范明确禁止——内容会被忽略 - 如果依赖 DOM,别把
<script src="..."></script>放在里又没加defer,否则大概率遇到document.getElementById(...) is null
什么时候必须加 defer 或 async
默认行为下,浏览器会暂停 HTML 解析去下载并执行脚本,这对首屏渲染很不友好。现代页面基本都要干预。
-
defer:脚本异步下载,但按出现顺序在 DOM 解析完成后、DOMContentLoaded前执行——适合有执行顺序依赖的插件(比如先加载 jQuery,再加载依赖它的插件) -
async:完全异步,下载完立刻执行,不保证顺序——适合统计、埋点等独立脚本 - 两者都只对带
src的<script></script>生效;内联脚本加了也没用
示例:<script src="/js/zepto.min.js" defer></script>,这样 Zepto 就能在 DOM 就绪后立即可用,又不会卡住页面渲染。
动态加载 script 的常见坑
用 JS 创建 <script></script> 标签来加载外部脚本(比如实现按需加载插件),看似灵活,实则容易漏掉关键环节。
- 必须手动监听
load和error事件,不能只靠onload回调——某些低版本浏览器不支持 - 动态插入的脚本默认是 async 行为,但执行时机不可控;若需顺序执行,得串行创建(或用 Promise.all + 动态 import)
- 重复插入同一 URL 的
<script></script>标签,浏览器通常只执行一次,但不会复用已定义的全局变量(比如第二次插入时window.$可能还是 undefined) - 注意 CSP 策略:如果服务端启用了
script-src 'self',动态创建的 script 会被拦截,控制台报Refused to load the script
加载失败怎么排查和兜底
网络抖动、CDN 故障、路径拼错都会导致脚本加载失败,但页面可能看起来“一切正常”,直到某个功能点点击无响应。
- 给
<script src="..."></script>加上onerror="console.error('Failed to load plugin.js')",至少让问题暴露在控制台 - 检查 Network 面板里的 Initiator 列,确认是不是这个
<script></script>标签发起的请求;有时是其他脚本(比如 webpack runtime)偷偷加载了同名资源 - 如果插件提供 UMD 版本,优先使用它;避免因模块系统不匹配(如 AMD vs CommonJS)导致导出对象不可见
- 加载完成后,别只检查
typeof xxx === 'function',还要验证实际行为,比如调用xxx.version或xxx.init()是否真的不报错
真正麻烦的从来不是“怎么加 script 标签”,而是加载完成后的状态是否可靠、错误是否可观测、失败后有没有 fallback 路径——这些往往被压缩进一行 src 属性里,却决定了整个插件链路能不能走通。


















