前言:
【CommonJs】exports,modules.exports,require的区别
📌概念
1. CommonJS 概念
历史:早期 JavaScript 主要跑在浏览器,没有模块系统;Node.js 为了管理代码,引入了 CommonJS 规范。
核心特点:
- 用
require()
导入模块 - 用
module.exports
或exports
导出模块 - 模块在运行时加载(同步加载)
- 每个文件就是一个独立作用域
👉 示例:
javascript
// foo.js
module.exports = { a: 1 };
// bar.js
const foo = require('./foo');
console.log(foo.a); // 1
2. ESM (ES Module) 概念
历史:ES6(2015)
标准化了 JavaScript 原生模块系统,叫 ESM。
核心特点:
- 用
import
导入 - 用
export
导出 - 模块在 编译时 静态解析(比 CommonJS 更高效)
- 支持
tree-shaking
(去掉没用到的代码) - 既能在浏览器直接运行,也能在 Node.js(新版本)中使用
👉 示例:
javascript
// foo.js
export const a = 1;
// bar.js
import { a } from './foo.js';
console.log(a); // 1
📌区别
CommonJS vs ESM 的区别
对比维度 | CommonJS | ESM |
---|---|---|
语法 | require / module.exports | import/ export |
加载时机 | 运行时加载(同步) | 编译时加载(静态) |
导出方式 | 整个对象(module.exports) | 默认导出 + 具名导出 |
导入结果 | require() 拿到的是对象副本 | import 拿到的是 绑定的引用 |
Tree Shaking | ❌ 不支持 | ✅ 支持(Webpack、Rollup 等) |
是否动态 | ✅ 可以写 require(someVar) 动态导入 | ❌ 只能静态导入(不过有 import() 动态语法) |
执行顺序 | 按代码执行顺序加载 | 静态依赖分析后再执行 |
Node 默认支持 | ✅ (默认) | ✅ (需 .mjs 后缀 或 package.json "type": "module" ) |
浏览器支持 | ❌ 原生不支持(需打包工具) | ✅ 现代浏览器原生支持 <script type="module"> |
📌使用场景
场景
CommonJS:
- 主要在 Node.js 老项目中使用(兼容性好)
- 适合脚本、工具类项目
ESM:
- 现代前端开发的主流选择(React, Vue, Angular 全部用 ESM)
- Tree-shaking、静态分析、跨平台更优
- Node.js 16+ 也推荐逐步迁移到 ESM
直观理解
CommonJS 像是:"我执行到这里,才去加载另一个文件"。
ESM 像是:"我在编译时就知道要依赖哪些文件,先解析好,再运行"。
1. CommonJS (Node 里默认用的)
写法:require
导出方式:
javascript
// foo.js
// 方法1:exports
exports.a = 1;
exports.sayHi = () => console.log('hi');
// 方法2:module.exports
module.exports = {
b: 2,
sayBye: () => console.log('bye')
};
导入方式:
javascript
const foo = require('./foo.js');
console.log(foo.a); // 1
foo.sayHi(); // hi
console.log(foo.b); // 2
foo.sayBye(); // bye
👉 CommonJS 里只有 require,没有 import {} 的语法。
2. ES Module (ESM,现代写法)
写法:import
导出方式:
javascript
// foo.mjs 或者 package.json 里加 "type": "module"
// 默认导出(只能有一个)
export default function() {
console.log('default fn');
}
// 具名导出(可以有多个)
export const a = 1;
export function sayHi() { console.log('hi'); }
导入方式:
javascript
// 默认导入
import foo from './foo.js';
foo(); // default fn
// 具名导入
import { a, sayHi } from './foo.js';
console.log(a); // 1
sayHi(); // hi
3. 关键区别:import xxx vs import { xxx }
写法 | 对应导出 | 特点 |
---|---|---|
import xxx from './foo.js' | export default ... | 只能对应 一个默认导出 |
import { xxx } from './foo.js' | export const xxx = ... / export function xxx... | 对应 具名导出,可以同时导入多个 |
import * as all from './foo.js' | 所有导出(默认 + 具名) | 用对象的方式访问 |
4. CommonJS 和 ESM 混用情况
Node 现在也支持 import
,但规则有点复杂:
如果模块是用module.exports = { ... }
导出的:
javascript
// foo.js (CommonJS)
module.exports = { a: 1, b: 2 };
// ESM 导入
import foo from './foo.js'; // 默认导入
console.log(foo.a); // 1
import { a } from './foo.js'; // ❌ 会报错,找不到具名导出
如果模块是用exports.a = ...
导出的:
结果和上面一样,import {a}
在 ESM 里也不生效。
👉 所以 CommonJS 导出的对象 = ESM 的默认导出。
记忆口诀:
- CommonJS →
module.exports
导出啥,require()
就拿到啥。 - ESM →
export default
对应import xxx
;export const xxx
对应import { xxx }
。