26/04/23 03:55:52
模块
模块化是软件开发中的一种设计模式,其核心思想是将一个大型的程序划分成若干个独立、可复用的部分(模块),每个模块完成一个特定的功能。这种设计方式可以提高代码的可维护性、可测试性和可扩展性。
一、模块化的优点
- 可维护性:模块之间相互独立,修改一个模块不会影响到其他模块。
- 可复用性:模块可以在多个项目中重复使用。
- 可测试性:模块独立后,可以针对每个模块进行单元测试。
- 团队协作:多人开发时,不同开发者可以专注于不同的模块。
- 命名空间隔离:避免全局变量污染。
二、ES Module (ESM) 和 CommonJS Module (CJS) 的区别
| 特性 | ES Module (ESM) | CommonJS Module (CJS) |
|---|---|---|
| 标准 | ECMAScript 6+ 标准 | Node.js 自定义规范 |
| 加载方式 | 静态加载(编译时加载) | 动态加载(运行时加载) |
| 使用场景 | 浏览器和现代 Node.js 环境 | 主要用于 Node.js 环境 |
| 语法 | import / export | require() / module.exports |
| 异步支持 | 支持异步加载模块(如通过 import() 动态导入) | 同步加载模块(不适合浏览器环境) |
| 模块导出内容 | 导出的是值的引用(响应式) | 导出的是值的拷贝 |
| 性能优化 | 更适合静态分析和 Tree-shaking | 不利于 Tree-shaking,难以优化 |
| 默认支持的工具链 | Webpack、Rollup、Vite 等现代构建工具 | Node.js 原生支持 |
三、总结
- ES Module 是现代 JavaScript 的官方模块系统,适用于浏览器和新版本的 Node.js,具有更好的性能和可优化性。
- CommonJS 是早期 Node.js 使用的模块系统,虽然在 Node.js 中仍然广泛使用,但逐渐被 ESM 替代。
- 在选择模块系统时,建议根据项目目标平台(浏览器/Node.js)、构建工具以及是否需要 Tree-shaking 等特性来决定使用哪种模块系统。
模块导出引用和拷贝的区别
在 JavaScript 中,值的引用(Reference) 和 值的拷贝(Copy) 是两种不同的数据传递方式。理解它们的区别有助于更准确地掌握模块之间数据交互的行为。
一、CommonJS 导出的是“值的拷贝”
- CommonJS 在模块加载时会立即执行模块代码,并把导出的值进行浅拷贝。
- 也就是说,导入模块中得到的是原模块导出值的一个快照。
- 如果原模块后续修改了导出的值,导入模块不会感知到这些变化。
示例:
js
// module.js
let count = 0;
exports.count = count;
setTimeout(() => {
count++;
}, 1000);
// main.js
const { count } = require('./module');
console.log(count); // 输出 0即使 module.js 中 count 被修改为 1,main.js 中的 count 仍然是初始的 0。
二、ES Module 导出的是“值的引用”
- ES Module 导出的是变量的引用地址,即导入模块和导出模块共享同一块内存中的值。
- 如果导出模块修改了变量,导入模块也会看到更新后的值。
示例:
js
// module.js
export let count = 0;
setTimeout(() => {
count++;
}, 1000);
// main.js
import { count } from './module.js';
console.log(count); // 初始为 0
setTimeout(() => {
console.log(count); // 输出 1
}, 2000);在这个例子中,main.js 中的 count 值会随着 module.js 的修改而改变。
三、对比总结
| 特性 | CommonJS(值的拷贝) | ES Module(值的引用) |
|---|---|---|
| 数据类型 | 原始值会拷贝,对象/数组是浅拷贝 | 引用地址,指向同一内存区域 |
| 模块更新感知 | 不会响应源模块的变化 | 会响应源模块的变化 |
| 加载时机 | 运行时同步加载 | 编译时静态加载 |
| 动态更新能力 | 不支持 | 支持 |
四、实际应用建议
- 如果你希望模块间共享状态并动态更新,使用 ES Module 更合适。
- 如果你需要模块导出的是固定配置或初始化后的快照,使用 CommonJS 可能更符合预期。
结合你的项目目标平台(浏览器/Node.js)以及对模块行为的需求,选择合适的模块系统非常重要。
在 JavaScript 中,ES Module (ESM) 和 CommonJS (CJS) 的模块导出机制本质上是不同的:
- ESM 导出的是变量的引用(响应式)
- CJS 导出的是值的拷贝(非响应式)
但如果你希望:
- 在 ESM 中导出值的拷贝(不响应源模块更新)
- 在 CJS 中导出值的深拷贝(避免对象/数组引用共享)
你可以通过手动处理来实现这些目标。
一、ES Module:如何导出“浅拷贝”或“深拷贝”
✅ 场景:你希望导出一个变量的拷贝(不是引用)
js
// module.js
let obj = { count: 0 };
setTimeout(() => {
obj.count++;
}, 1000);
// 导出浅拷贝(只拷贝一层)
export const copyObj = { ...obj };
// 或者导出深拷贝(使用 JSON 序列化,适用于可序列化的对象)
export const deepCopyObj = JSON.parse(JSON.stringify(obj));js
// main.js
import { copyObj, deepCopyObj } from './module.js';
console.log(copyObj); // { count: 0 }
console.log(deepCopyObj); // { count: 0 }
setTimeout(() => {
console.log(copyObj); // { count: 0 }(不会变)
console.log(deepCopyObj); // { count: 0 }(不会变)
}, 2000);⚠️ 注意:JSON 深拷贝会丢失函数、
undefined、特殊对象(如Date,RegExp)等,需谨慎使用。可以考虑用第三方库如lodash.cloneDeep()实现更安全的深拷贝。
二、CommonJS:如何导出“深拷贝”
✅ 场景:你希望导出的对象/数组是深拷贝
js
// module.js
let obj = { count: 0 };
setTimeout(() => {
obj.count++;
}, 1000);
// 导出深拷贝
exports.obj = JSON.parse(JSON.stringify(obj));
// 或使用 lodash
// const _ = require('lodash');
// exports.obj = _.cloneDeep(obj);js
// main.js
const { obj } = require('./module');
console.log(obj); // { count: 0 }
setTimeout(() => {
console.log(obj); // 仍然是 { count: 0 }
}, 2000);三、总结对比表
| 需求 | 模块系统 | 实现方式 | 示例代码 |
|---|---|---|---|
| ESM 导出浅拷贝 | ES Module | 使用 {...obj} | export const copy = {...obj}; |
| ESM 导出深拷贝 | ES Module | JSON.parse(JSON.stringify(obj)) 或 lodash.cloneDeep() | export const deepCopy = JSON.parse(JSON.stringify(obj)); |
| CJS 导出深拷贝 | CommonJS | 同上 | exports.obj = JSON.parse(JSON.stringify(obj)); |
四、建议
- 如果你不希望模块之间共享状态变化,不要直接导出变量名,而是返回新对象。
- 对于复杂对象,推荐使用成熟的深拷贝工具(如
lodash.cloneDeep)来确保数据隔离。 - 在现代项目中,尽量使用 ES Module + 构建工具(如 Vite/Webpack),便于静态分析和优化。