动态插入脚本的核心难点是“何时可用”,需区分资源加载、解析执行、DOM可用性三阶段;应合理设置async/defer、用Promise链控制顺序、验证依赖就绪而非仅监听onload、处理跨域上下文丢失,并统一管理生命周期防重复加载。

动态插入脚本的核心难点不在“怎么插”,而在“什么时候能用”——即加载是否完成、依赖是否就绪、执行是否在预期上下文中。关键要区分 资源加载、解析执行 和 DOM 可用性 三个阶段,并主动控制它们的协同。
明确 script 标签的 async 与 defer 行为差异
动态创建的 <script></script> 默认具有 async=true(现代浏览器中),即下载不阻塞 HTML 解析,但一旦下载完成就立即执行,不保证顺序。若需按插入顺序执行(例如依赖前一个脚本导出的全局变量),必须显式设置:
-
script.async = false—— 回退为经典同步行为(会阻塞后续 DOM 解析,慎用) -
script.defer = true—— 仅对同域脚本有效,延迟到 DOM 解析完成后、DOMContentLoaded前按插入顺序执行 - 更稳妥的做法:不用
defer,改用Promise链控加载,例如loadScript(src).then(() => loadScript(nextSrc))
监听加载完成而非依赖“onload”事件本身
script.onload 只表示脚本文件已下载并执行完毕,但不等于其中定义的函数/模块已就绪(比如它内部异步初始化、或等待 DOM 元素挂载)。应结合业务语义做二次确认:
- 若脚本暴露全局变量(如
window.MyLib),可在onload后轮询或使用MutationObserver监听该变量出现 - 若脚本导出 ES 模块(通过
import()动态导入),直接 await 其返回值,天然具备执行时序保障 - 避免只监听
onload就调用依赖方法——先检查typeof MyLib.init === 'function'再执行
规避跨域脚本的执行上下文丢失问题
从 CDN 加载的第三方脚本(如 analytics、widget)常因 CORS 或浏览器策略导致无法访问其内部作用域,或 this 指向异常。此时不能假设它会自动绑定到 window:
- 优先选用支持
crossorigin="anonymous"的资源,并确保服务端返回Access-Control-Allow-Origin: * - 不要依赖脚本内
document.write或隐式eval,这些在现代 CSP 策略下大概率被拦截 - 对关键第三方 SDK,采用其官方推荐的异步加载模式(如 Google Analytics 的
gtag()队列机制),用函数队列代替即时调用
统一管理脚本生命周期,避免重复加载与竞态
多次调用动态插入逻辑可能导致同一脚本加载多次、执行多次,引发状态混乱或报错:
- 维护一个
loadedScripts = new Set(),插入前先检查src是否已存在 - 对每个脚本封装成返回 Promise 的函数(如
loadLodash()),内部用Promise.resolve(cachedResult)缓存成功结果 - 若页面中多个模块都需某 SDK,由顶层容器统一加载并广播就绪事件(如
customEvent('sdk-ready', { name: 'stripe' })),其余模块订阅而非重载

















