ES6模块和CommonJS模块有哪些区别

0 阅读3分钟

核心区别概览

特性ES6模块(ESM)CommonJS(CJS)
标准来源ECMAScript标准Node.js社区规范
设计目标浏览器 + 服务器服务器端(Node.js)
语法import/exportrequire()/module.exports
加载时机编译时静态解析运行时动态加载
加载方式异步(浏览器)同步(Node.js)
值引用动态绑定(引用)值拷贝
文件扩展名.mjs或设置type: "module".cjs.js

详细区别

1. 语法差异

ES6模块(ESM)

// 导出
export const name = 'Alice';
export function hello() {};
export default someFunction;

// 导入
import { name, hello } from './module.js';
import someFunction from './module.js';
import * as Module from './module.js';

CommonJS(CJS)

// 导出
module.exports = someFunction;
// 或
exports.name = 'Alice';

// 导入
const someFunction = require('./module');
const { name } = require('./module');

2. 加载时机

ES6模块CommonJS
解析时机编译时静态分析运行时动态加载
特点支持静态分析,利于tree-shaking灵活,支持条件加载
示例javascript<br>// 正确<br>import { foo } from './module';<br><br>// 错误(语法错误)<br>if (condition) {<br> import { bar } from './module';<br>}<br>javascript<br>// 正确<br>let module;<br>if (condition) {<br> module = require('./moduleA');<br>} else {<br> module = require('./moduleB');<br>}<br>

3. 值传递机制

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 (同步更新)

CommonJS:值拷贝

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

// main.js
const counter = require('./counter.js');

console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0 (不更新,拷贝的是原始值)

4. 循环依赖处理

ES6模块

// a.mjs
import { b } from './b.mjs';
export const a = 'a';

// b.mjs
import { a } from './a.mjs';
console.log(a); // undefined (因为a尚未初始化)
export const b = 'b';

CommonJS

// a.js
exports.a = 'a';
const b = require('./b.js');
console.log(b.b); // 'b'

// b.js
exports.b = 'b';
const a = require('./a.js');
console.log(a.a); // 'a' (得到部分导出的对象)

5. 运行环境支持

环境ES6模块CommonJS
现代浏览器✅ 原生支持(需<script type="module">❌ 不支持
Node.js✅ v13.2.0+(需.mjs扩展名或package.json设置)✅ 原生支持
旧版浏览器❌ 需打包工具转换❌ 需打包工具转换

6. 顶级this

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

// CommonJS模块中
console.log(this); // 指向module.exports(非严格模式)或undefined(严格模式)

7. 互操作性

ESM中导入CJS

// ES模块中导入CommonJS模块
import cjsModule from './commonjs-module.cjs';
// 注意:CommonJS的默认导出需要整个导入

CJS中导入ESM(Node.js)

// CommonJS中导入ES6模块(Node.js中必须使用异步)
async function main() {
  const esmModule = await import('./es-module.mjs');
}
main();

实际应用建议

何时使用哪种?

场景推荐
前端项目(React/Vue)✅ ES6模块(通过Webpack等打包工具)
Node.js新项目✅ ES6模块(.mjs或设置"type": "module"
Node.js旧项目/库✅ CommonJS(兼容性考虑)
跨平台库开发🔄 建议提供双模式或打包为UMD

配置示例

package.json 配置:

{
  "name": "my-package",
  "type": "module", // 使用ES6模块
  "exports": {
    ".": {
      "import": "./index.mjs",  // ES6模块入口
      "require": "./index.cjs"  // CommonJS入口
    }
  }
}

迁移注意事项

  1. 文件扩展名
    • .mjs → ES6模块
    • .cjs → CommonJS模块
  2. __dirname和__filename
// ES6模块中替代方案
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
  1. 包导入
// CommonJS中
const path = require('path');

// ES6模块中
import path from 'path'; // 或使用 createRequire

总结

方面关键区别
发展方向ES6模块是未来标准,CommonJS是历史标准
性能ES6模块支持tree-shaking,打包体积更小
灵活性CommonJS动态加载更灵活
生态系统前端生态已全面转向ESM,Node.js正在过渡
学习成本ES6模块语法更现代、一致

现代开发建议:新项目优先使用ES6模块,老项目根据需求逐步迁移。对于库开发,考虑同时支持两种模块系统以最大化兼容性。