在 Sea.js 模块化开发中,exports 与 module.exports 这两个接口暴露方式,常常让刚接触模块加载的开发者感到困惑。表面上它们都用于对外提供模块功能,但一旦用错,模块将无法正确导出任何内容。下面通过几个具体实例,系统梳理它们的区别、正确用法以及常见陷阱。

关于这两者的关系,Sea.js 官方 issue #242(https://github.com/seajs/seajs/issues/242)中有详细讨论。简单归纳两点核心要点:
exports是module.exports的辅助引用对象。如果只用exports暴露接口,需要理解它的本质——它只是一个指向module.exports的引用。module.exports可以直接作为模块的真实接口使用,适用场景更加灵活。
exports 对象详解
exports 本身是一个对象,常用于向外提供模块接口。最常见的做法是给它的属性逐一赋值:
define(function(require, exports) {
// 对外暴露 foo 属性
exports.foo = 'bar';
// 对外暴露 doSomething 方法
exports.doSomething = function() {};
});
除了通过 exports 添加成员,还可以直接用 return 返回接口对象:
define(function(require) {
// 通过 return 直接返回接口
return {
foo: 'bar',
doSomething: function() {}
};
});
如果整个模块只有一个 return 语句,还可以进一步简化为直接传入对象字面量:
define({
foo: 'bar',
doSomething: function() {}
});
这种写法特别适合定义 JSONP 模块,既简洁又清晰。
⚠️ 特别注意:下面这种写法是错误的!
define(function(require, exports) {
// 错误用法!!!千万不要这样写
exports = {
foo: 'bar',
doSomething: function() {}
};
});
正确的做法应该是使用 return 或者直接给 module.exports 赋值:
define(function(require, exports, module) {
// 正确的写法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
❓ 为什么直接给 exports 赋值会失效?
核心原因在于:exports 只是 module.exports 的一个引用副本。在 factory 内部重新为 exports 赋值时,仅仅改变了 exports 的指向,而 module.exports 依然指向原来的空对象。最终模块返回的是 module.exports 所引用的对象,你新赋值的对象被完全丢弃。
module.exports 对象详解
module.exports 才是当前模块真正的对外接口。传给 factory 构造方法的 exports 参数,实质上就是 module.exports 对象的一个引用。
只依赖 exports 参数来提供接口,在某些场景下会受限。例如,当模块需要返回一个类的实例时,必须借助 module.exports:
define(function(require, exports, module) {
// exports 是 module.exports 的一个引用
console.log(module.exports === exports); // true
// 重新给 module.exports 赋值
module.exports = new SomeClass();
// 此时 exports 不再等于 module.exports
console.log(module.exports === exports); // false
});
⚠️ 重要提示:对 module.exports 的赋值必须同步执行,不能放在异步回调中。 下面这种写法是典型的陷阱:
// x.js
define(function(require, exports, module) {
// 错误用法:异步赋值无效
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
});
在其他模块中引用 x.js 时,会立即执行 require('./x'),但此时 module.exports 的赋值还在定时器队列中排队,根本无法获取到最终结果:
// y.js
define(function(require, exports, module) {
var x = require('./x');
// 无法立刻获取模块 x 的属性 a
console.log(x.a); // undefined
});
综上所述,exports 适用于简单场景下逐个添加属性;module.exports 则适合需要替换整个对象或异步构造模块的情况。只要牢记“引用”这一本质,大多数问题就能迎刃而解。
