module.exports 属性与 exports 变量的区别

发布于 2023-05-10 15:20:40 字数 4295 浏览 77 评论 0

一、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 变量指向一个值,因为这样等于切断了 exportsmodule.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 的指向,即切断了 exportsmodule.exports 的联系。但是我们模块对外输出的接口是 module.exports,所以 2️⃣ 得到的是初始值 {}

如果你觉得 exportsmodule.exports 之间的区别很难分清,一个简单的处理方法,就是放弃使用 exports,只使用 module.exports

*我个人也没觉得 exports 的写法有多方便,哈哈。

4. 总结

非常简单,就三点:

  • module.exports 初始值为一个空对象 {}
  • exports 是指向的 module.exports 的引用;
  • require() 返回的是 module.exports 而不是 exports

还是那句话,如果你觉得 exportsmodule.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.exportsexports 重新指向 module.exports

三、References

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

始于初秋

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

eins

文章 0 评论 0

世界等同你

文章 0 评论 0

毒初莱肆砂笔

文章 0 评论 0

初雪

文章 0 评论 0

miao

文章 0 评论 0

qq_zQQHIW

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文