一文搞懂exports和module.exports的关系和区别

咱们知道NodeJS遵循 CommonJS 的规范,使用 require 关键字来加载模块,使用 exportsmodule.exports 来导出模块,那么这两个导出又有什么关系或者区别呢?html

其实,在node执行一个文件时,会给这个文件内生成一个 exports 对象和一个 module 对象,而这个module 对象又有一个属性叫作 exportsnode

新建一个index.js文件 执行 node index.js 命令api

console.log(exports)
console.log(module)
复制代码

能够看出控制台的输出结果以下: bash

在这里插入图片描述
咱们再来看看 exportsmodule.exports 有什么关系呢? 咱们在index.js 文件中添加一句代码

console.log(exports === module.exports)
复制代码

会发现结果是 true 这说明,文件开始执行的时候,它们是指向同一块内存区域的app

graph LR;
A[exports] ; B[内存:xxx]; C[module.exports]
    A-->B
    C-->B
复制代码

当文件执行完毕的时候,只有module.exports 变量被返回了,以便后续被其余模块 require 引用,为了证实这个观点,咱们能够新建一个文件 index2.js 进行测试 index2.js测试

exports.a = 1
复制代码

而后在 index3.js 中引用ui

const module2 = require('./index2')
console.log(module2)
复制代码

控制台输出: { a: 1 } 而后咱们在 index2.js 中添加代码:spa

exports.a = 1
module.exports = {
  b:2
}
复制代码

在这里同时使用两个导出方法,查看控制台输出结果为 { b: 2 } 此时,咱们继续在 index2.js 文件中添加code

console.log(exports === module.exports)
复制代码

结果为false,此时的 exportsmodule.exports 已经不是指向同一块内存地址了,由于前面的代码里面,咱们使用了cdn

module.exports = {
  b:2
}
复制代码

这致使了 module.exports从新指向了新的内存地址, 可是当咱们执行 node index3.js 查看index3.js 的运行结果时,看到的是 {b:2} 而不是 {a:1} 证实了咱们上面的观点: 只有module.exports 变量被返回了 所以,初始化的状态,咱们能够用以下代码来帮助理解:

var module = {
	exports:{}
}
var exports = module.exports
复制代码

而最终的导出结果是 module.exports 这个对象. 到了这里,可能有人又会有疑问,为啥以前不少的模块都是须要引入才能使用,可是exportsmodule.exports 咱们没有引用却能直接使用? 这个问题的答案咱们能够从Node的官方文档中找到答案,

在这里插入图片描述
Node API 传送门: module wrapper 这里,Node的官方文档里面提到, NodeJS 应用在文件被执行前会被包装一层:

(function(exports,require,module,__filename,__dirname){
  ...
})
复制代码

在进行了头尾封装后,各模块之间进行了做用域的隔离,避免了污染全局变量,经过头尾封装,实现了

  • 保持顶层变量(用 var、 const 或 let 定义)做用在模块范围内,而不是全局对象。
  • 提供一些看似全局的但其实是模块特定的变量
    • 实现了从模块中导出值的 module 和 exports 对象
    • 包含模块绝对文件名(__filename)和目录路径(__dirname)的快捷变量

总结:

  1. exports 对象是 module 对象的一个属性,在初始时 module.exportsexports 指向同一块内存区域
  2. 模块导出的是 module.exports , exports 只是对它的引用,在不改变exports 内存的状况下,修改exports 的值能够改变 module.exports 的值
  3. 导出时尽可能使用 module.exports ,以避免由于各类赋值致使的混乱
相关文章
相关标签/搜索