一、CommonJS 模块规范
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS 规范规定,每个模块内部,module
变量代表当前模块。这个变量是一个对象,它的 exports
属性(即 module.exports
)是对外的接口。加载某个模块,其实是加载该模块的 module.exports
属性。
CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
1. module.exports 属性
module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。
2. exports 变量
为了方便,Node 为每个模块提供一个 exports 变量,它执行 module.exports。这等同于在每个模块头部,有一行这样的命令:var exports = module.exports
,我们可以通过以下方式来验证
console.log(exports === module.exports); // true
所以在对外输出模块接口时,可以向 exports 对象添加属性方法。
module.exports.age = 20
module.exports.getAge = function() {}
// 相当于
exports.age = 20
exports.getAge = function() {}
但是不能直接将 exports 变量指向一个值,因为这样等于切断了 exports 与 module.exports 的联系。
// 以下写法无效,因为 exports 不再指向 module.exports 了。
exports = function() {};
3. module.exports 与 exports 的使用
当一个模块的对外接口,只是一个单一的值时,不能使用 exports 输出,只能使用 module.exports 输出。
// moduleA.js
// 1️⃣ 正确 ✅
module.exports = function() {};
// 2️⃣ 错误 ❎
exports = function() {};
导入模块看结果:
// other.js
var moduleA = require('moduleA.js');
console.log(moduleA);
// 两种写法打印的值分别为:
// 1️⃣ 预期结果 ✅ ƒ () { console.log('moduleD'); }
// 2️⃣ 非预期结果 ❎ {}
分析结果:
首先我们要知道 module.exports 的初始值是 {}
,当执行 exports = function() {};
赋值时,无论赋值的是基本数据类型还是引用数据类型,都将改变 exports 的指向,即切断了 exports 与 module.exports 的联系。但是我们模块对外输出的接口是 module.exports,所以 2️⃣ 得到的是初始值 {}
。
如果你觉得 exports 与 module.exports 之间的区别很难分清,一个简单的处理方法,就是放弃使用 exports,只使用 module.exports。
* 我个人也没觉得 exports 的写法有多方便,哈哈。
4. 总结
非常简单,三点:
- module.exports 初始值为一个空对象 {}
- exports 是指向的 module.exports 的引用
- require() 返回的是 module.exports 而不是 exports
还是那句话,如果你觉得 exports 与 module.exports 之间的区别很难分清,一个简单的处理方法,就是放弃使用 exports,只使用 module.exports。
二、require() 扩展话题
以下案例源自知乎某帖回答,这里。
关于 require() 的解释
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
注意实现顺序,也就是下面代码为什么不成功的原因。
// moduleA.js
module.exports = function() {};
// 为什么这段配置不成功?你们有 BUG!!!
exports.abc = 'abc';
require()
的时候,是先通过 exports.abc
获取, 然后通过 module.exports
直接覆盖了原有的 exports
,所以 exports.abc = 'abc'
就无效了。
一般库的封装都是 exports=module.exports= _;
(underscore 的例子)。
原因很简单,通过 exports = module.exports 让 exports 重新指向 module.exports 。
三、参考
全文终,The end.