
本文介绍一种无需依赖全局轮询或页面整体加载事件的可靠方式,仅等待指定 文件(如大型库)完全加载并定义好所需函数后,立即执行后续逻辑,避免竞态问题。
本文介绍一种无需依赖全局轮询或页面整体加载事件的可靠方式,仅等待指定 `<script>` 文件(如大型库)完全加载并定义好所需函数后,立即执行后续逻辑,避免竞态问题。</script>
在浏览器环境中,当多个 <script></script> 标签按顺序写入 HTML 时,默认同步阻塞行为仅在 type="module" 或显式声明 defer/async 时才会被打破。但实际开发中,若 first.js 体积较大、网络较慢,或服务器响应延迟,即使写成:
<script src="first.js"></script> <script src="second.js"></script>
仍可能出现 second.js 执行时 funcFromFirstJs 尚未挂载到全局作用域的问题——这是因为浏览器虽按顺序请求脚本,但 HTML 解析器对 <script></script> 的“加载完成”判断仅基于资源获取与解析结束,不保证其内部所有副作用(如函数定义、全局变量赋值)已同步就绪;尤其在某些压缩工具或动态导出模式下,初始化逻辑可能被延迟。
你当前采用的轮询方案(callIfReady + setTimeout)虽能兜底,但存在明显缺陷:
- ❌ 不精确:无法感知真实加载完成时机,纯靠猜测重试;
- ❌ 资源浪费:空转占用主线程,影响性能与可维护性;
- ❌ 风险不可控:若
funcFromFirstJs永远未定义(如路径错误、语法异常),将无限循环。
✅ 更优解是:让 second.js 主动加载 first.js,并监听其原生 load 事件。该事件在脚本下载、解析、执行全部完成后触发,严格保证其导出的全局符号(如 funcFromFirstJs)已就绪:
// second.js
function callWhenReady() {
// 此处可安全调用 first.js 提供的任何 API
funcFromFirstJs();
}
// 动态创建 script 元素,确保加载流程可控
const script = document.createElement('script');
script.src = './first.js'; // 使用相对路径或完整 URL
script.onload = callWhenReady;
script.onerror = () => {
console.error('Failed to load first.js');
// 可选:降级处理或上报监控
};
document.head.appendChild(script); // 推荐 append 到 head,避免阻塞 body 渲染⚠️ 关键注意事项:
- 必须从 HTML 中移除原始的
<script src="first.js"></script>标签,否则会重复加载,造成冗余请求与潜在冲突; -
onload回调在脚本执行完毕后立即触发,因此funcFromFirstJs必然已定义(前提是first.js本身无运行时异常); - 若
first.js依赖其他资源(如 JSON 配置、Web Worker),需在其内部自行处理,onload不代表其子依赖完成; - 如需支持 IE9+,可保留
onreadystatechange兜底,但现代项目建议直接使用onload(所有主流浏览器均支持)。
? 进阶建议:
- 长期来看,强烈推荐引入构建工具(如 Vite、Webpack、esbuild),通过静态分析实现模块化打包与 tree-shaking,从根本上消除跨脚本依赖时序问题;
- 若必须保持多文件部署,可考虑将
first.js改为 ES 模块(type="module"),并在second.js中用import('./first.js').then(...)动态导入——语义更清晰,且天然支持 Promise 错误处理与加载状态控制。
此方法轻量、标准、无侵入,精准满足“只等一个脚本,不关心其余资源”的核心诉求。


















