JavaScript模块化完整指南
JavaScript模块化完整指南
OHNIIJavaScript模块化完整指南
核心流程(记忆关键)
记忆口诀:立即执行,CommonJS同步,AMD异步,ES Module静态
核心对比:CommonJS(Node.js)vs ES Module(浏览器/现代工具)
第一部分:核心概念
1. 什么是模块化?
1.1 定义
- 将一个复杂的程序拆分成多个独立的模块
- 每个模块负责特定的功能
- 模块之间通过接口进行通信
1.2 为什么需要模块化?
没有模块化的问题:
- 全局污染:所有变量都在全局作用域
- 命名冲突:变量名可能重复
- 依赖管理:不知道文件的加载顺序
- 难以维护:代码耦合严重
模块化的优势:
- 避免命名冲突(独立作用域)
- 更好的代码组织(按功能拆分)
- 按需加载(提升性能)
- 高复用性(模块可重用)
- 高维护性(模块独立,易于维护)
2. 主流模块化规范(面试重点)
2.1 CommonJS(Node.js)
核心特点:
- 同步加载、运行时加载、值拷贝
- 主要用于服务端(Node.js)
语法:
1 | // 导出 |
关键特性:
- 模块缓存:同一模块只加载一次
- 值拷贝:导出的是值的拷贝,不会动态更新
2.2 ES Module(官方标准)
核心特点:
- 编译时加载(静态加载)
- 值引用(动态绑定)
- 支持 Tree Shaking
- 浏览器和现代工具的标准
语法:
1 | // 导出 |
关键特性:
- 静态结构:编译时确定依赖
- 值引用:导出的是值的引用,会动态更新
- 只读:导入的变量不能修改
3. CommonJS vs ES Module 深度对比
3.1 核心区别
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载时机 | 运行时 | 编译时 |
| 加载方式 | 同步 | 异步 |
| 输出 | 值拷贝 | 值引用 |
| 动态加载 | 支持 | 需要 import() |
| this | module.exports | undefined |
| Tree Shaking | 不支持 | 支持 |
| 循环依赖 | 返回已执行部分 | 返回 undefined |
| 使用场景 | Node.js | 浏览器、现代工具 |
3.2 加载时机
CommonJS(运行时加载):
- 执行到 require 时才加载
- 可以动态加载
- 可以在条件语句中使用
ES Module(编译时加载):
- 编译时就确定依赖关系
- import 必须在顶层
- 动态导入需要使用 import()
3.3 输出方式
CommonJS(值拷贝):
- 导出的是值的拷贝
- 基本类型不会动态更新
- 需要通过方法获取最新值
ES Module(值引用):
- 导出的是值的引用
- 会动态更新
- 但导入的变量是只读的
3.4 Tree Shaking
为什么 ES Module 支持 Tree Shaking?
- 静态结构:编译时就知道导出了什么
- 可以进行静态分析
- 确定哪些代码被使用,哪些没有被使用
为什么 CommonJS 不支持?
- 动态加载:运行时才知道导出什么
- 无法确定哪些代码被使用
- 可能通过对象属性访问(如
utils.unused)
4. 模块化最佳实践
4.1 选择建议
使用 ES Module:
- 新项目
- 浏览器环境
- 需要 Tree Shaking
- 现代构建工具
使用 CommonJS:
- Node.js 项目
- 老项目
- 需要动态加载
4.2 编码规范
推荐:
- 使用命名导出(利于 Tree Shaking)
- 避免循环依赖
- 合理组织模块结构
- 使用绝对路径(配置别名)
不推荐:
- 使用 export default(不利于 Tree Shaking)
- 导入整个模块(import * as)
- 使用 index 作为 key
第二部分:面试准备
5. 面试口述版本
5.1 CommonJS 和 ES Module 的区别?(高频)
“CommonJS 和 ES Module 是两种主流的模块化规范,核心区别有四点:
第一是加载时机。CommonJS 是运行时加载,执行到 require 时才加载模块,所以可以在条件语句中使用。ES Module 是编译时加载,在代码执行前就确定了依赖关系,import 必须在顶层。
第二是输出方式。CommonJS 输出的是值的拷贝,基本类型不会动态更新,需要通过方法获取最新值。ES Module 输出的是值的引用,会动态更新,但导入的变量是只读的。
第三是 Tree Shaking。CommonJS 不支持 Tree Shaking,因为是运行时加载,无法进行静态分析。ES Module 支持 Tree Shaking,因为是编译时加载,编译器可以确定哪些代码被使用。
第四是使用场景。CommonJS 主要用于 Node.js 服务端。ES Module 是官方标准,用于浏览器和现代构建工具,是未来的趋势。”
5.2 为什么 ES Module 支持 Tree Shaking?(高频)
“ES Module 支持 Tree Shaking 的核心原因是静态结构。
首先,ES Module 是编译时加载,在代码执行前就确定了依赖关系。编译器可以分析出哪些模块被导入,哪些导出被使用。
其次,import 和 export 必须在顶层,不能在条件语句或函数中使用。这保证了模块结构是静态的、确定的。
最后,通过静态分析,编译器可以确定哪些代码被使用,哪些没有被使用,从而删除未使用的代码。
相比之下,CommonJS 是运行时加载,可以在条件语句中使用 require,编译器无法确定哪些代码会被使用,所以不支持 Tree Shaking。”
6. 高频追问
Q1: 为什么 ES Module 支持 Tree Shaking?
答案:
ES Module 支持 Tree Shaking 的核心原因是静态结构:
- 编译时加载:在代码执行前就确定了依赖关系
- 静态分析:编译器可以分析出哪些代码被使用
- 确定性:import 和 export 必须在顶层,不能在条件语句中
CommonJS 不支持的原因:
- 运行时加载:执行到 require 时才加载
- 动态性:可以在条件语句中使用
- 不确定性:无法确定哪些代码被使用
Q2: 如何处理循环依赖?
答案:
最佳实践是避免循环依赖:
方法1:提取公共模块
- 将共享的代码提取到单独的模块
- 两个模块都依赖这个公共模块
方法2:延迟导入
- 在函数内部导入,而不是在模块顶层
- 延迟到真正需要时才导入
方法3:使用依赖注入
- 通过参数传递依赖
- 在外部组装依赖关系
Q3: module.exports 和 exports 的区别?
答案:
exports 是 module.exports 的引用:
1 | 原理: |
建议统一使用 module.exports,避免混淆。
Q4: 如何在 Node.js 中使用 ES Module?
答案:
三种方法:
- 使用 .mjs 扩展名,Node.js 会自动识别
- package.json 设置
"type": "module"(推荐) - 使用 Babel 或 TypeScript 转译
7. 实战经验(3个典型场景)
经验1:优化打包体积
问题: 第三方库体积过大
方案:
1 | // 错误:导入整个库 |
结果: 打包体积从 2MB 降到 500KB
经验2:解决循环依赖
问题: 两个模块相互依赖,导致加载错误
方案: 提取公共模块
1 | // 错误:A 依赖 B,B 依赖 A |
结果: 消除循环依赖,代码结构更清晰
经验3:模块化迁移
问题: 老项目使用 CommonJS,需要迁移到 ES Module
方案:
- package.json 设置
"type": "module" - 修改导入导出语法
- 逐步迁移,测试验证
结果: 支持 Tree Shaking,打包体积减少 30%
第三部分:代码参考
8. CommonJS 核心用法
1 | // 导出 |
9. ES Module 核心用法
1 | // 导出 |
10. 记忆路线图
1 | JavaScript 模块化(面试重点) |
11. 总结
核心要点:
- 模块化解决全局污染、命名冲突、依赖管理问题
- CommonJS:同步加载、值拷贝、运行时加载,适合 Node.js
- ES Module:编译时加载、值引用、支持 Tree Shaking,是未来趋势
- Tree Shaking 的关键是静态结构,编译时确定依赖
- 现代项目推荐使用 ES Module
记忆口诀:
- CommonJS 同步拷贝,运行时加载
- ES Module 静态引用,编译时分析
- Tree Shaking 靠静态,删除未使用代码
- 新项目用 ES Module,Node.js 用 CommonJS

