javascript es modules 的模块作用域是指每个 .mjs 或 type="module" 的脚本文件都拥有独立的顶层作用域,其中的变量、函数和类不会自动泄露到全局 window 对象或其他脚本中。要实现跨模块通信,必须通过 export 显式导出接口,并在其他模块中使用 import 语句按需引入。这种机制确保了代码的封装性,避免了命名冲突,是构建现代前端应用的基础。

ES Modules 与 Script 标签在变量可见性上有何区别
使用 script 标签加载的脚本默认处于全局作用域,顶层声明的变量会挂载到 window 对象上;而 ES Modules 严格隔离了顶层作用域,模块内的标识符对外部不可见。这意味着在 Module 中定义的变量无法通过 window.varName 访问,从而防止了全局污染。

// global.js (Script) var sharedVar = "I am global"; console.log(window.sharedVar); // "I am global" // module.mjs (Module) let privateVar = "I am private"; export const publicVar = "I am exported"; console.log(window.privateVar); // undefined console.log(window.publicVar); // undefined
上述代码展示了两种加载方式下的变量可见性差异。在 global.js 中,sharedVar 成为 window 的属性;而在 module.mjs 中,即使 publicVar 被导出,它依然不属于 window 对象,外部只能通过 import 获取。
立即学习“Java免费学习笔记(深入)”;
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
动态导入 import() 如何影响模块加载时机
静态 import 必须在模块顶层使用,且会在解析阶段同步加载依赖;而动态 import() 是一个返回 Promise 的表达式,可以在函数内部或条件分支中调用,实现按需加载。这种方式允许根据运行时状态决定加载哪些模块,优化首屏性能。

function loadFeature(isAdmin) {
if (isAdmin) {
import('./admin-module.mjs').then(module => {
module.init();
});
}
}
loadFeature(true); // 仅在管理员模式下加载
动态导入打破了静态结构的限制,使得模块加载变得灵活。在上面的示例中,只有当 isAdmin 为 true 时,才会触发对 admin-module.mjs 的网络请求和执行。这种模式常用于路由懒加载或特性开关场景。
模块中循环引用会导致什么具体结果
当模块 A 导入模块 B,而模块 B 又导入模块 A 时,JavaScript 引擎采用“部分求值”策略处理循环依赖。如果模块 A 在导入 B 之前已经导出了某些成员,B 可以安全地使用这些已导出的值;但如果 B 尝试访问 A 尚未执行到的导出成员,则将获得 undefined。这种行为不同于 CommonJS 的缓存机制,更依赖于执行顺序。
// a.mjs
import { bVal } from './b.mjs';
export const aVal = 'A';
console.log('A loaded, bVal is:', bVal);
// b.mjs
import { aVal } from './a.mjs';
export const bVal = 'B';
console.log('B loaded, aVal is:', aVal); // 输出: B loaded, aVal is: A
在循环引用场景中,a.mjs 首先执行,此时它导入 b.mjs。b.mjs 开始执行并导入 a.mjs,由于 a.mjs 已经导出了 aVal,b.mjs 能正确获取到 'A'。随后 b.mjs 完成执行并导出 bVal,a.mjs 继续执行并获取到 'B'。这种机制保证了循环依赖下的代码可运行性,但开发者需明确知晓值的可用性取决于执行进度。

















