module.exports 属性与 exports 变量的区别
一、CommonJS 模块规范
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS 规范规定,每个模块内部,module
变量代表当前模块。这个变量是一个对象,它的 exports
属性(即 module.exports
)是对外的接口。加载某个模块,其实是加载该模块的 module.exports
属性。
CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
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()
的解释:To illustrate the behavior, imagine this hypothetical implementation of require(), which is quite similar to what is actually done by 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
。
三、References
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论