Skip to content
26/04/23 03:55:52

模块

模块化是软件开发中的一种设计模式,其核心思想是将一个大型的程序划分成若干个独立、可复用的部分(模块),每个模块完成一个特定的功能。这种设计方式可以提高代码的可维护性、可测试性和可扩展性。

一、模块化的优点

  1. 可维护性:模块之间相互独立,修改一个模块不会影响到其他模块。
  2. 可复用性:模块可以在多个项目中重复使用。
  3. 可测试性:模块独立后,可以针对每个模块进行单元测试。
  4. 团队协作:多人开发时,不同开发者可以专注于不同的模块。
  5. 命名空间隔离:避免全局变量污染。

二、ES Module (ESM) 和 CommonJS Module (CJS) 的区别

特性ES Module (ESM)CommonJS Module (CJS)
标准ECMAScript 6+ 标准Node.js 自定义规范
加载方式静态加载(编译时加载)动态加载(运行时加载)
使用场景浏览器和现代 Node.js 环境主要用于 Node.js 环境
语法import / exportrequire() / 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.jscount 被修改为 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 ModuleJSON.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),便于静态分析和优化。

夏月影,风悠扬...