ES 模块默认隔离作用域,直接赋值 window.add = add 会因模块脚本异步执行而失效;需通过延迟执行、显式等待或动态导入确保全局函数在非模块脚本运行前已就绪。
es 模块默认隔离作用域,直接赋值 `window.add = add` 会因模块脚本异步执行而失效;需通过延迟执行、显式等待或动态导入确保全局函数在非模块脚本运行前已就绪。
在现代 JavaScript 中,<script type="module"> 默认以异步、延迟(defer)方式执行,且其执行时机晚于普通 <script> 标签(即使 HTML 中后者写在后面)。这导致你在模块内给 window.add 赋值的操作,总是在非模块脚本尝试调用 window.add(1, 2) 之后才发生——因此报错 Uncaught TypeError: window.add is not a function。
✅ 正确做法:确保执行时序可控
方案 1:将依赖逻辑也改为模块(推荐)
最符合现代实践的方式是统一使用模块系统,避免混合模式:
<!-- index.html --> <script type="module"> const add = (a, b) => a + b; window.add = add; // 现在 window.add 已就绪 </script> <script type="module"> // 同样是模块,保证在 window.add 设置后执行 console.log(window.add(1, 2)); // ✅ 输出 3 </script>
✅ 优势:语义清晰、无竞态、可维护性强;✅ 符合 ES 模块设计初衷。
方案 2:使用 DOMContentLoaded 或 setTimeout(0) 延迟执行(兼容旧场景)
若必须保留非模块脚本(如遗留代码),可在其中等待全局函数就绪:
<script type="module">
const add = (a, b) => a + b;
window.add = add;
console.log('✅ Global add registered');
</script>
<script>
// 确保 DOM 加载完成后再检查(但不保证模块已执行)
document.addEventListener('DOMContentLoaded', () => {
if (typeof window.add === 'function') {
console.log(window.add(1, 2)); // ✅ 安全调用
} else {
console.warn('⚠️ window.add not ready yet — consider using modules consistently.');
}
});
</script>更健壮的等待方式(带超时兜底):
<script>
function waitForGlobalAdd(timeout = 1000) {
return new Promise((resolve, reject) => {
const start = Date.now();
const check = () => {
if (typeof window.add === 'function') {
resolve(window.add);
} else if (Date.now() - start > timeout) {
reject(new Error('Timeout: window.add never became available'));
} else {
setTimeout(check, 10);
}
};
check();
});
}
waitForGlobalAdd()
.then(add => console.log(add(1, 2)))
.catch(err => console.error(err));
</script>❌ 不推荐:仅靠 setTimeout(0) 或顺序书写
<!-- 错误示例 —— 仍可能失败 --> <script type="module"> window.add = (a, b) => a + b; </script> <script> setTimeout(() => console.log(window.add(1,2)), 0); // ❌ 不可靠:模块执行本身是微任务/延迟任务,时机不可控 </script>
⚠️ 注意事项与最佳实践
- 模块脚本永不阻塞 HTML 解析,且总是异步执行,与 <script defer> 行为类似,但优先级更低;
- window 是浏览器环境的全局对象,在模块中修改 window.xxx 是合法的,但不能替代模块化导出机制(如 export / import);
- 若需跨模块共享逻辑,请使用标准 export default + import,而非污染全局命名空间;
- 在构建工具(Vite、Webpack)中,可通过 define 或 globalThis 配置注入全局变量,更安全可控。
总结
将函数挂载到 window 并非错误,但必须正视模块的异步本质。优先采用全模块化架构;若需临时暴露,务必通过事件监听、Promise 等显式同步机制协调执行时序,杜绝“凭顺序猜测执行顺序”的反模式。

















