主题
ES Module
ES Module 是 JavaScript 的标准模块方案。它解决的是“代码如何拆分、导入、导出和复用”的问题。
模块解决什么问题
模块化主要用于:
- 拆分文件职责
- 显式声明依赖关系
- 暴露对外接口
- 避免变量互相污染
在没有模块时,脚本通常直接挂到全局作用域,文件之间的依赖关系也不清晰。
ES Module 的基本模型
一个模块通常做两件事:
- 通过
export暴露内容 - 通过
import使用其他模块的内容
js
// math.js
export function add(a, b) {
return a + b
}js
// main.js
import { add } from './math.js'
console.log(add(1, 2))模块默认是独立作用域
模块文件有自己的作用域,顶层声明不会自动变成全局变量:
js
const count = 1上面的 count 只属于当前模块,其他文件不能直接访问,除非显式导出。
静态分析是 ES Module 的关键特征
import 和 export 都是静态结构,这意味着在代码真正执行前,运行环境就能先分析依赖关系。
这带来几个直接结果:
import必须写在顶层- 导入导出关系在解析阶段就能确定
- 有利于 tree-shaking、依赖预分析和循环依赖处理
js
import { add } from './math.js'这不是普通函数调用,而是模块语法本身。
浏览器里如何使用模块
浏览器中需要显式声明模块脚本:
html
<script type="module" src="./main.js"></script>模块脚本和普通脚本有几个常见区别:
- 自动按模块规则解析
import和export - 默认使用严格模式
- 顶层
this是undefined - 同一个模块只会执行一次
模块加载和执行可以怎么理解
可以粗略理解成两个阶段:
- 解析依赖图
- 按依赖顺序执行模块
在解析阶段,运行环境会先找到所有静态导入,再递归分析整个依赖关系。
导入的不是拷贝值
ES Module 导入的是 live binding,不是简单的值拷贝。
js
// counter.js
export let count = 0
export function increment() {
count += 1
}js
// main.js
import { count, increment } from './counter.js'
console.log(count) // 0
increment()
console.log(count) // 1这说明导入方读到的是导出方当前的绑定结果。
和 CommonJS 的区别
最常见的区别有:
- ES Module 使用
import/export - CommonJS 使用
require()/module.exports - ES Module 偏静态
- CommonJS 偏运行时加载
这也是很多“默认导出行为不一致”“互操作看起来奇怪”的来源。
什么时候适合关注模块边界
在这些场景里更需要明确模块设计:
- 公共工具函数拆分
- 大型页面或功能模块拆分
- 组件库或工具库对外暴露 API
- 需要按需加载代码
如果只是一个很小的单文件脚本,模块化收益可能不明显。
使用建议
- 一个模块只承担一类职责。
- 对外接口尽量稳定,不要把内部细节全部导出。
- 先分清“模块语法”与“打包工具行为”不是同一层概念。
- 遇到模块问题时,先确认当前环境到底是浏览器原生 ESM、Node.js ESM,还是打包器处理后的结果。
