esmodule模块加载机制确保模块只执行一次:首次导入时执行脚本并缓存module record,后续导入复用同一导出对象,实现天然单例与实时绑定。

ESModule 的模块加载机制本身已确保模块只执行一次,导入多次也不会重复执行初始化代码。关键在于理解其缓存行为和正确使用方式。
模块加载天然单例
ESModule 在首次导入时执行模块脚本,并将 export 对象缓存到模块记录(Module Record)中;后续所有 import 都复用同一份导出对象,不会重新执行顶层代码。
- 即使多个文件分别
import { foo } from './utils.js',utils.js只执行一次 - 导出的值是“活绑定”(live binding),修改一个地方的导出变量,其他导入处会同步反映(对
let/const声明的变量有效) - 注意:原始类型(如数字、字符串)赋值后不会自动同步,只有对象/函数等引用类型能体现响应性
避免手动重复加载的常见误操作
真正引发“重复加载感”的往往不是模块机制本身,而是开发者误用或绕过标准加载流程:
- 用
import()动态导入同一路径多次——虽然仍走缓存,但每次调用返回的是 Promise,可能造成逻辑冗余 - 在循环或条件分支中反复写
import静态语句(语法不允许,编译报错) - 把模块路径拼成不同字符串(如
'./config.js'和'config.js'被视为不同模块) - 开发时热更新(HMR)或构建工具配置不当,导致模块被意外重载(属于环境问题,非语言特性)
确保唯一性的实用建议
保持模块路径一致性和加载时机合理即可:
- 统一使用相对路径或别名(配合打包工具如 Vite/Webpack 的 alias 配置),避免路径歧义
- 动态导入前加简单判断,例如用闭包缓存 Promise:
const loadUtils = (() => { let p; return () => p ?? (p = import('./utils.js')); })(); - 导出对象尽量封装为不可变结构,或通过函数返回新实例(如工厂函数),避免意外共享状态污染
- 调试时查看浏览器 DevTools 的 Network 或 Sources 面板,确认模块实际只请求一次(HTTP 层面)
与 CommonJS 的区别要留意
CommonJS 的 module.exports 是浅拷贝式导出,而 ESModule 是实时绑定引用。若你从 CommonJS 迁移过来,需注意:
- 不要期望
export let count = 0在其他模块中通过count++全局生效(它会生效,但仅限该绑定) - 避免混用
require和import同一模块(尤其在 Node.js 中),可能导致双实例 - Node.js 中启用
"type": "module"或使用.mjs后缀,确保运行在纯 ESM 环境
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











