Node.js 模块系统
CommonJS 的 require/exports、模块缓存、循环依赖。
Key Points`require` 是同步加载 + 执行 + 缓存;`module.exports` 才是真正的导出对象。
一个模块是什么
Node wraps every file in a function, so top-level var does not pollute the global scope:
JavaScript
(function (exports, require, module, __filename, __dirname) {
// 你的文件内容
});| Name | Meaning |
|---|---|
exports | module.exports 的初始引用 |
require | 加载其他模块的函数 |
module | 当前模块对象,含 exports |
__filename | 当前文件的绝对路径 |
__dirname | 当前文件所在目录 |
导出的两种写法
JavaScript
// math.js —— 挂属性
exports.add = (a, b) => a + b;
exports.sub = (a, b) => a - b;
// 或者整体替换(推荐)
module.exports = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
};JavaScript
const { add } = require('./math');
console.log(add(1, 2));Output
3exports = { add } is a no-op — it only rebinds a local variable; module.exports is untouched. Use module.exports = {...} or exports.add = ....缓存
JavaScript
// counter.js
let n = 0;
module.exports = () => ++n;
// a.js
const c = require('./counter');
console.log(c(), c()); // 1 2A module executes once, on first require, and the same object is returned afterwards. Module-level variables are process-wide singletons. To get a fresh instance, export a factory.
循环依赖
JavaScript
// a.js
exports.done = false;
const b = require('./b');
console.log('在 a 里,b.done =', b.done);
exports.done = true;
// b.js
exports.done = false;
const a = require('./a');
console.log('在 b 里,a.done =', a.done); // false —— 拿到的是半成品
exports.done = true;Output
在 b 里,a.done = false
在 a 里,b.done = trueWhen b requires a, a is only half-executed, so it sees whatever was exported so far (done = false). Circular dependencies are almost always a design smell — use injection or extract a third module.
一个模块是什么
Node 把每个文件包进一个函数里执行,所以文件里的 var 不会污染全局:
JavaScript
(function (exports, require, module, __filename, __dirname) {
// 你的文件内容
});| 名称 | 含义 |
|---|---|
exports | module.exports 的初始引用 |
require | 加载其他模块的函数 |
module | 当前模块对象,含 exports |
__filename | 当前文件的绝对路径 |
__dirname | 当前文件所在目录 |
导出的两种写法
JavaScript
// math.js —— 挂属性
exports.add = (a, b) => a + b;
exports.sub = (a, b) => a - b;
// 或者整体替换(推荐)
module.exports = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
};JavaScript
const { add } = require('./math');
console.log(add(1, 2));Output
3exports = { add } 这样写是无效的——它只是把局部变量指向了新对象,module.exports 没变。必须写 module.exports = {...} 或 exports.add = ...。缓存
JavaScript
// counter.js
let n = 0;
module.exports = () => ++n;
// a.js
const c = require('./counter');
console.log(c(), c()); // 1 2模块在第一次 require 时执行一次,之后返回同一个对象。所以模块级变量是进程级单例。想每次拿新实例,导出一个工厂函数。
循环依赖
JavaScript
// a.js
exports.done = false;
const b = require('./b');
console.log('在 a 里,b.done =', b.done);
exports.done = true;
// b.js
exports.done = false;
const a = require('./a');
console.log('在 b 里,a.done =', a.done); // false —— 拿到的是半成品
exports.done = true;Output
在 b 里,a.done = false
在 a 里,b.done = trueb 里 require('./a') 时 a 还没执行完,拿到的是当时已导出的部分(done = false)。循环依赖几乎总是设计问题,用依赖注入或拆出第三个模块来解。