动态插入的script标签加defer无效,因HTML规范规定defer仅对静态HTML中带src的外部脚本生效;其默认行为等同async,须用Promise封装并监听onload/onerror确保执行时机。

动态插入的<script>加defer根本不起作用</script>
浏览器对动态创建的 <script> 标签默认按 async=true 行为处理,哪怕你手动设置 script.defer = true,在 Chrome、Firefox、Safari 等现代浏览器中依然会被忽略——这不是 bug,是 HTML 规范明确规定的:defer 仅对**静态 HTML 中声明的、带 src 的外部脚本**生效。
常见错误现象:
- 控制台没报错,但
document.getElementById('app')返回null(DOM 还没就绪就执行了) - 多个动态脚本依赖顺序混乱,比如
lodash.js还没挂到window._,utils.js就开始执行 - 构建工具(如 Vite)把内联初始化代码注入到动态脚本里,误以为加了
defer就安全,结果照样阻塞或失效
用 Promise 封装动态加载并监听 onload
真正可控的方式是放弃依赖 HTML 属性,改用显式时机控制。核心逻辑:脚本插入后必须等 onload(成功)或 onerror(失败)触发,才能认为它已执行完毕。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 不要直接
head.appendChild(script)后就调用函数,此时init()必然报ReferenceError - 用
Promise包一层,确保后续逻辑只在脚本加载并执行完成后运行 - 示例代码(精简版):
function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = () => resolve(); script.onerror = () => reject(new Error(`Failed to load ${src}`)); document.head.appendChild(script); }); } // 使用 loadScript('vendor.js') .then(() => loadScript('app.js')) .then(() => init()); // 此时 vendor 和 app 都已执行完
需要保序且依赖 DOM 就绪?组合 useDeferred + DOMContentLoaded
如果脚本既要求 DOM 已就绪,又必须按顺序执行(比如初始化 canvas 或绑定事件),单靠 onload 不够——因为脚本可能先下载完、先执行,但 DOM 还没解析完。
这时得叠加两层保障:
-
loadScript()确保脚本文件已下载并执行 - 外层再 wrap 一层
DOMContentLoaded或检查document.readyState - 不要用
setTimeout盲等,容易漏判或过度延迟 - 更稳妥写法:
function loadScriptWithDOM(src) { return loadScript(src).then(() => { if (document.readyState === 'loading') { return new Promise(r => document.addEventListener('DOMContentLoaded', r)); } }); }
为什么 type="module" 也不能直接解决这个问题
<script type="module" src="x.js"></script> 在静态 HTML 中确实自带 defer 效果,但动态插入时:script.type = 'module' 后 append,行为仍是 async —— 模块的 defer 语义只在初始 HTML 解析阶段被识别,不适用于运行时创建。
容易踩的坑:
- 误以为
script.type = 'module'; script.defer = true;能双重保险 → 实际defer被忽略,type="module"也不触发 defer 时序 - 模块脚本默认有顶层 await,但动态插入时无法保证它在 DOM 就绪后才执行
- 若需模块化 + 动态 + DOM 就绪,推荐用
import():它天然返回 Promise,且执行时机由 JS 引擎控制,比手动插 script 更可靠
<script> 块的变量提升或赋值状态。哪怕你刚在上一个脚本里写了 window.myLib = {},动态插入的新脚本里也得重新检查 typeof window.myLib !== 'undefined',不能假设“插进去就接续”。



















