CommonJS 是什么?
CommonJS 是 Node.js 采用的模块规范,主要用于服务器端 JavaScript 开发。它使用同步加载模块的方式,因为服务器端文件都在本地,读取速度快。
核心语法
1. 导出模块(Exports)
CommonJS 提供了两种导出方式:
方式一:module.exports
javascript
// math.js
module.exports = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
// 或者导出单个函数/类
module.exports = function(a, b) {
return a + b;
};
// 或者导出一个类
module.exports = class Calculator {
add(a, b) {
return a + b;
}
};
方式二:exports
(是 module.exports
的引用)
javascript
// math.js
exports.add = function(a, b) {
return a + b;
};
exports.subtract = function(a, b) {
return a - b;
};
⚠️ 注意:exports
和 module.exports
的区别
javascript
// ❌ 错误用法 - 这样会切断 exports 和 module.exports 的引用关系
exports = function(a, b) {
return a + b;
};
// ✅ 正确用法
module.exports = function(a, b) {
return a + b;
};
// ✅ 或者
exports.add = function(a, b) {
return a + b;
};
2. 导入模块(Require)
javascript
// 导入整个模块
const math = require('./math');
math.add(1, 2);
// 使用解构导入
const { add, subtract } = require('./math');
add(1, 2);
// 导入核心模块
const fs = require('fs');
const path = require('path');
// 导入第三方模块
const express = require('express');
const lodash = require('lodash');
详细特性
3. 模块缓存
javascript
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};
// main.js
const counter1 = require('./counter');
const counter2 = require('./counter');
counter1.increment(); // 1
console.log(counter2.getCount()); // 1 - 同一个实例!
// 模块只会加载一次,后续 require 返回缓存的模块
console.log(counter1 === counter2); // true
4. 模块查找机制
javascript
// 1. 核心模块(优先级最高)
require('fs'); // Node.js 内置模块
// 2. 文件模块
require('./math'); // 当前目录下的 math.js
require('./math.js'); // 明确指定后缀
require('../utils/math'); // 上级目录
// 3. 文件夹模块
require('./myModule');
// 查找顺序:
// - ./myModule/package.json 的 main 字段
// - ./myModule/index.js
// - ./myModule/index.json
// - ./myModule/index.node
// 4. node_modules 模块
require('express');
// 查找顺序(从当前目录向上递归):
// - ./node_modules/express
// - ../node_modules/express
// - ../../node_modules/express
// - 直到根目录
5. 循环依赖处理
javascript
// a.js
console.log('a 开始');
exports.done = false;
const b = require('./b');
console.log('在 a 中,b.done =', b.done);
exports.done = true;
console.log('a 结束');
// b.js
console.log('b 开始');
exports.done = false;
const a = require('./a');
console.log('在 b 中,a.done =', a.done);
exports.done = true;
console.log('b 结束');
// main.js
const a = require('./a');
console.log('main 中,a.done =', a.done);
/* 输出:
a 开始
b 开始
在 b 中,a.done = false // a 还未执行完,只获取到部分导出
b 结束
在 a 中,b.done = true
a 结束
main 中,a.done = true
*/
6. 模块包装
每个模块在执行前,Node.js 会将代码包装在一个函数中:
javascript
(function(exports, require, module, __filename, __dirname) {
// 你的模块代码
const math = require('./math');
module.exports = { /* ... */ };
});
所以在模块中可以直接使用:
exports
- 导出对象的引用require
- 导入函数module
- 当前模块对象__filename
- 当前文件的绝对路径__dirname
- 当前文件所在目录的绝对路径
CommonJS vs ES6 模块
特性 | CommonJS | ES6 Module |
---|---|---|
语法 | require/module.exports |
import/export |
加载方式 | 同步加载 | 异步加载 |
加载时机 | 运行时加载 | 编译时加载 |
输出 | 值的拷贝 | 值的引用 |
this | 指向当前模块 | undefined |
使用环境 | Node.js | 浏览器和 Node.js |
动态导入 | 支持(天然) | 需要 import() |
javascript
// CommonJS - 运行时加载,可以动态 require
const moduleName = Math.random() > 0.5 ? './a' : './b';
const module = require(moduleName); // ✅ 可以
if (condition) {
const math = require('./math'); // ✅ 可以
}
// ES6 - 编译时加载,import 必须在顶层
import math from './math'; // ✅ 必须在顶层
if (condition) {
import math from './math'; // ❌ 不可以
import('./math').then(math => {}); // ✅ 需要用动态 import
}
实际应用示例
javascript
// logger.js - 工具模块
const chalk = require('chalk'); // 第三方库
class Logger {
info(msg) {
console.log(chalk.blue('[INFO]'), msg);
}
error(msg) {
console.log(chalk.red('[ERROR]'), msg);
}
}
module.exports = new Logger(); // 导出单例
// config.js - 配置模块
module.exports = {
port: process.env.PORT || 3000,
db: {
host: 'localhost',
port: 5432
}
};
// app.js - 主应用
const express = require('express');
const logger = require('./logger');
const config = require('./config');
const app = express();
app.listen(config.port, () => {
logger.info(`Server running on port ${config.port}`);
});