
javascript 模块默认隔离作用域,若需将函数暴露为全局变量,必须确保非模块脚本在模块执行完毕后再访问;直接赋值 window.add = add 有效,但因模块异步延迟执行,常导致访问时机错误。
javascript 模块默认隔离作用域,若需将函数暴露为全局变量,必须确保非模块脚本在模块执行完毕后再访问;直接赋值 window.add = add 有效,但因模块异步延迟执行,常导致访问时机错误。
在现代浏览器中,带有 type="module" 的 <script> 标签默认以 <strong>defer 方式执行:它会下载异步、执行延迟(DOM 解析完成后、且按顺序等待所有模块依赖就绪),因此其执行时机<strong>严格晚于普通 <script>(同步或无 defer 的脚本)。这正是你遇到 window.add is not a function 的根本原因——第二个 <script> 在模块尚未运行时就尝试调用 window.add。</script>
✅ 正确做法:确保全局访问发生在模块执行之后。有以下几种可靠方案:
方案一:统一使用模块(推荐)
将调用方也改为模块脚本,利用模块天然的执行时序保证:
<script type="module"> const add = (a, b) => a + b; window.add = add; // ✅ 成功挂载 </script><!-- 改为模块脚本,确保后执行 --><script type="module"> console.log(window.add(1, 2)); // ✅ 输出 3 </script>
方案二:监听模块完成事件(如需混合脚本)
若必须保留普通脚本,可借助 DOMContentLoaded 或显式延迟,但更稳妥的是主动通知:
<script type="module">
const add = (a, b) => a + b;
window.add = add;
// 触发自定义事件,通知全局环境已就绪
window.dispatchEvent(new Event('moduleReady'));
</script><script>
// 等待模块就绪后再执行
window.addEventListener('moduleReady', () => {
console.log(window.add(1, 2)); // ✅ 输出 3
});
</script>
方案三:使用 async + 显式等待(适用于动态加载)
若模块通过 import() 动态导入,可在 Promise 解析后挂载:
<script>
import('./math.js').then(({ add }) => {
window.add = add;
console.log(window.add(1, 2)); // ✅ 安全调用
});
</script>
⚠️ 注意事项:
- ❌ 避免依赖“脚本书写顺序”来控制执行时序(HTML 中 <script> 顺序 ≠ 执行顺序);</script>
- ✅ window.xxx = fn 本身完全合法且有效,问题永远在执行时机,而非赋值方式;
- ? 全局污染应尽量避免;优先考虑 ES 模块标准导出/导入,仅在兼容遗留代码或特定 API(如 Web Components、第三方 SDK 回调)时才导出至 window;
- ? 测试时可用 console.time() / console.timeEnd() 或 performance.now() 精确验证执行先后。
总结:导出函数到 window 并非反模式,而是需尊重模块的生命周期。掌握执行时序机制,配合模块化设计原则,即可安全、可控地桥接模块与全局环境。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











