Node.js 中 CommonJS 和 ES6 模块的区别

作者:Administrator 发布时间: 2026-05-13 阅读量:3 评论数:0

Node.js 中 CommonJS 和 ES6 模块的区别

1. 语法差异

CommonJS (CJS)

// 导出
module.exports = { a: 1, b: 2 };
// 或
exports.a = 1;
exports.b = 2;

// 导入
const { a, b } = require('./module');
const module = require('./module');

ES6 模块 (ESM)

// 导出
export const a = 1;
export const b = 2;
// 或
export default { a: 1, b: 2 };

// 导入
import { a, b } from './module.js';
import module from './module.js';
import * as module from './module.js';

2. 加载时机

CommonJS

// 运行时加载(动态加载)
const moduleName = process.env.NODE_ENV === 'production' 
  ? './prod-module' 
  : './dev-module';
const module = require(moduleName); // ✅ 允许动态路径

// 循环引用示例
// a.js
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');

// b.js
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');

ES6 模块

// 编译时加载(静态加载)
import module from './module.js'; // ✅ 必须静态路径

// ❌ 不允许动态路径(直接使用会报错)
const moduleName = './module.js';
import module from moduleName; // SyntaxError

// ✅ 使用动态导入(返回 Promise)
const moduleName = './module.js';
import(moduleName).then(module => {
  // 使用模块
});

// 循环引用示例
// a.mjs
console.log('a 开始');
export const done = false;
import { done as bDone } from './b.mjs';
console.log('在 a 中,b.done = %j', bDone);
export const done = true;
console.log('a 结束');

// b.mjs
console.log('b 开始');
export const done = false;
import { done as aDone } from './a.mjs';
console.log('在 b 中,a.done = %j', aDone);
export const done = true;
console.log('b 结束');

3. 值类型差异

CommonJS - 值拷贝

// counter.js
let count = 0;
function increment() {
  count++;
}
module.exports = { count, increment };

// main.js
const counter = require('./counter');
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0 ❌ 不改变

ES6 模块 - 值引用

// counter.mjs
export let count = 0;
export function increment() {
  count++;
}

// main.mjs
import { count, increment } from './counter.mjs';
console.log(count); // 0
increment();
console.log(count); // 1 ✅ 实时更新

4. this 指向

// CommonJS
console.log(this); // {} 空对象
console.log(this === exports); // false
console.log(this === module.exports); // false
console.log(this === global); // false

// ES6 模块
console.log(this); // undefined

package.json 配置

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module", // 或 "commonjs" 或不设置
  "exports": {
    ".": {
      "import": "./dist/esm/index.js", // ESM 入口
      "require": "./dist/cjs/index.js" // CJS 入口
    }
  }
}

文件扩展名

// .js 文件(默认 CommonJS)
// 除非 package.json 有 "type": "module"

// .mjs 文件(总是 ESM)
import { something } from './module.mjs';

// .cjs 文件(总是 CommonJS)
const something = require('./module.cjs');

6. 互操作(互相导入)

ESM 导入 CJS

// cjs-module.js
module.exports = {
  hello: 'world',
  func: () => 'Hello'
};

// esm-module.mjs
import cjsModule from './cjs-module.js'; // ✅ 默认导入
console.log(cjsModule.hello); // 'world'

// 或使用 createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjsModule = require('./cjs-module.js');

CJS 导入 ESM(需要异步)

// esm-module.mjs
export const value = 42;
export default 'default export';

// cjs-module.js
// ❌ 不能直接 require
// const esm = require('./esm-module.mjs'); // Error

// ✅ 使用动态 import(返回 Promise)
(async () => {
  const esm = await import('./esm-module.mjs');
  console.log(esm.value); // 42
  console.log(esm.default); // 'default export'
})();

CommonJS 缓存

// module.js
console.log('模块加载');
module.exports = { data: Math.random() };

// main.js
const m1 = require('./module'); // 输出: 模块加载
const m2 = require('./module'); // 无输出,使用缓存
console.log(m1 === m2); // true
console.log(m1.data === m2.data); // true

// 清除缓存
delete require.cache[require.resolve('./module')];
const m3 = require('./module'); // 再次输出: 模块加载

ES6 模块缓存

// module.mjs
console.log('ESM 模块加载');
export const data = Math.random();

// main.mjs
import * as m1 from './module.mjs'; // 输出: ESM 模块加载
import * as m2 from './module.mjs'; // 无输出,使用缓存

// 无法清除 ES6 模块缓存

8. 顶层 await

ES6 模块支持

// module.mjs
export const data = await fetchData();

// main.mjs
import { data } from './module.mjs';
console.log(data); // 直接使用,无需 async 函数

CommonJS 不支持

// ❌ 报错
const data = await fetchData();
module.exports = { data };

9. 目录结构示例

复制代码project/
├── package.json
├── src/
│   ├── cjs/
│   │   ├── index.js
│   │   └── utils.js
│   └── esm/
│       ├── index.mjs
│       └── utils.mjs
├── dist/
│   ├── cjs/
│   └── esm/
└── dual-package/
    ├── index.cjs  # CommonJS 入口
    ├── index.mjs  # ESM 入口
    └── package.json

10. 性能差异

特性

CommonJS

ES6 模块

加载时机

运行时

编译时

静态分析

困难

容易

Tree Shaking

❌ 不支持

✅ 支持

循环引用

部分加载

引用未完成值

缓存清除

✅ 支持

❌ 不支持

11. 最佳实践

双模式包配置

{
  "name": "my-dual-package",
  "version": "1.0.0",
  "exports": {
    "import": "./dist/esm/index.js",
    "require": "./dist/cjs/index.js"
  },
  "main": "./dist/cjs/index.js",
  "module": "./dist/esm/index.js",
  "types": "./dist/types/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "build:cjs": "tsc --module commonjs --outDir dist/cjs",
    "build:esm": "tsc --module esnext --outDir dist/esm",
    "build": "npm run build:cjs && npm run build:esm"
  }
}

条件导入示例

// 检测环境
const isESM = typeof module === 'undefined' || 
  (typeof process !== 'undefined' && 
   process.versions?.node?.split('.')[0] >= '14');

// 条件导出
export async function loadModule() {
  if (isESM) {
    const { default: esmModule } = await import('./esm-module.mjs');
    return esmModule;
  } else {
    const cjsModule = require('./cjs-module.js');
    return cjsModule;
  }
}

12. 迁移策略

// 步骤1:修改 package.json
{
  "type": "module"  // 或保持默认,使用 .mjs 扩展名
}

// 步骤2:重命名文件
// .js → .cjs (CommonJS 文件)
// .js → .mjs (ESM 文件)

// 步骤3:更新导入导出语法
// require() → import
// module.exports → export

// 步骤4:处理 __dirname 和 __filename
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

总结对比表

特性

CommonJS

ES6 模块

语法

require() / module.exports

import / export

加载时机

运行时

编译时

值类型

值拷贝

值引用

顶层 await

❌ 不支持

✅ 支持

动态导入

内置支持

import() 函数

循环引用

部分加载

引用绑定

文件扩展名

.js / .cjs

.js / .mjs

Node.js 支持

原生支持

Node.js 13.2+

浏览器支持

❌ 不支持

✅ 原生支持

Tree Shaking

❌ 不支持

✅ 支持

严格模式

默认非严格

默认严格

建议:新项目使用 ES6 模块,旧项目逐步迁移,库包提供双模式支持。

评论