require时,exports和module.exports的区别你真的懂吗?

本文深入探讨了Node.js中require的运行机制,包括其同步加载特性、缓存策略及exports与module.exports的区别。通过实例代码讲解require加载模块的过程,帮助读者理解模块加载的内部原理。

640?wx_fmt=gif

640?wx_fmt=jpeg

面试会问

require 的运行机制和缓存策略你了解吗?

require 加载模块的是同步还是异步?谈谈你的理解

exports 和 module.exports 的区别是什么?

require 加载模块的时候加载的究竟是什么?

require

提到 exports 和 module.exports 我们不得不提到 require 关键字。大家哦读知道 Node.js 遵循 CommonJS 规范,使用 require 关键字来加载模块。

require 重复引入问题

问题:不知道小伙伴们在使用 require 引入模块的时候有没有相关,多个代码文件中多次引入相同的模块会不会造成重复呢?

因为在 C++ 中通常使用#IFNDEF等关键字来避免文件的重复引入,但是在 Node.js 中无需关心这一点,因为 Node.js 默认先从缓存中加载模块,一个模块被加载一次之后,就会在缓存中维持一个副本,如果遇到重复加载的模块会直接提取缓存中的副本,也就是说在任何时候每个模块都只在缓存中有一个实例。

require 加载模块的时候是同步还是异步?

先回答问题,同步的!但是面试官要是问你为什么是同步还是异步的呢?其实这个答案并不是固定的,但是小伙伴们可以通过这几方面给面试官解释。

  1. 一个作为公共依赖的模块,当然想一次加载出来,同步更好

  2. 模块的个数往往是有限的,而且 Node.js 在 require 的时候会自动缓存已经加载的模块,再加上访问的都是本地文件,产生的IO开销几乎可以忽略。

require() 的缓存策略

Node.js 会自动缓存经过 require 引入的文件,使得下次再引入不需要经过文件系统而是直接从缓存中读取。不过这种缓存方式是经过文件路径定位的,即使两个完全相同的文件,但是位于不同的路径下,会在缓存中维持两份。可以通过

console.log(require.cache)

获取目前在缓存中的所有文件。

exports 与 module.exports 区别

js文件启动时

在一个 node 执行一个文件时,会给这个文件内生成一个 exports 和 module 对象, 而module又有一个 exports 属性。他们之间的关系如下图,都指向一块{}内存区域。

exports = module.exports = {};

看一张图理解这里更清楚:

640?wx_fmt=jpeg

require()加载模块

require()加载模块的时候我们来看一段实例代码

//koala.js	
let a = '程序员成长指北';	
console.log(module.exports); //能打印出结果为:{}	
console.log(exports); //能打印出结果为:{}	
exports.a = '程序员成长指北哦哦'; //这里辛苦劳作帮 module.exports 的内容给改成 {a : '程序员成长指北哦哦'}	
exports = '指向其他内存区'; //这里把exports的指向指走	
//test.js	
const a = require('/koala');	
console.log(a) // 打印为 {a : '程序员成长指北哦哦'}

看上面代码的打印结果,应该能得到这样的结论:

require导出的内容是module.exports的指向的内存块内容,并不是exports的。简而言之,区分他们之间的区别就是 exports 只是 module.exports的引用,辅助后者添加内容用的。用内存指向的方式更好理解。

官网中的一个例子

看一下官方文档中exports的应用

640?wx_fmt=jpeg我们经常看到这样的写法:

exports = module.exports = somethings

上面的代码等价于:

module.exports = somethings	
exports = module.exports

原理很简单,即 module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports 即可。

使用的一点建议

建议:在使用的时候更建议大家使用module.exports(根据下面的例子也能得出)

Node.js 认为每个文件都是一个独立的模块。如果你的包有两个文件,假设是“a.js” 和“b.js”,然后“b.js” 要使用“a.js” 的功能,“a.js” 必须要通过给 exports 对象增加属性来暴露这些功能:

// a.js	
exports.verifyPassword = function(user, password, done) { ... }

完成这步后,所有需要“a.js” 的都会获得一个带有“verifyPassword” 函数属性的对象:

// b.js	
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }

然而,如果我们想直接暴露这个函数,而不是让它作为某些对象的属性呢?我们可以覆写 exports 来达到目的,但是我们绝对不能把它当做一个全局变量:

// a.js	
module.exports = function(user, password, done) { ... }

注意到我们是把“exports” 当做 module 对象的一个属性。“module.exports” 和“exports” 这之间区别是很重要的,而且经常会使 Node.js 新手踩坑。

640?wx_fmt=png

Node.js 高级进阶之 fs 文件模块学习

来,告诉你Node.js究竟是什么?

Node进阶-探究不在V8堆内存中存储的Buffer对象

说Node.js做后端开发,stream有必要了解下

深入理解Javacript从作用域作用域链开始

【JS必知必会】高阶函数详解与实战

640?wx_fmt=jpeg

交流学习

大家好,我是koala,公众号「程序员成长指北」作者。公众号为您打造优质学习路线,并且会推送超级优质文章。加入我们一起学习吧!

640?wx_fmt=jpeg

在看点这里

640?wx_fmt=gif640?wx_fmt=png

<think>好的,我现在需要解决用户关于JavaScript中exportsmodule.exports区别的问题。首先,我需要回忆自己之前学过的相关知识,可能还需要查阅一些资料来确保准确性。 用户的问题主要集中在Node.js环境下,这两个对象的区别。我记得exportsmodule.exports的一个引用,但具体细节可能容易混淆。根据用户提供的引用,特别是引用[4]提到,exports是引用module.exports的值,改变exportsmodule.exports不会改变,而模块导出的是module.exports。这似乎意味着如果直接给exports赋值,就会断开它与module.exports的联系,导致导出为空对象,如引用[1]中的例子所示。 接下来,我需要整理这些信息,形成一个清晰的解释结构。可能需要从两者的关系开始,说明初始exports指向module.exports,这样通过exports添加属性是有效的。但一旦直接给exports赋值新对象,引用就会断开,导致后续修改无效。同,导出始终以module.exports为准,所以直接覆盖exports不会影响导出结果。 还要注意用户提供的代码示例,比如在a.js中exports = function,结果在b.js中require得到空对象,这应该是因为exports被重新赋值,而module.exports仍然是原来的空对象,所以导出的是空对象。而正确的方式应该是修改module.exports或者保持exports的引用。 另外,需要提到常见的用法,比如导出函数或构造函数,通常使用module.exports = ...,而导出多个方法或属性,可以用exports.xxx = ...。同,要强调不能混用,否则可能导致覆盖问题。 可能还需要对比两者,列出区别注意事项,帮助用户更好地理解。例如,初始关系、赋值的影响、导出结果等。最后,总结最佳实践,避免常见的错误。 需要确保回答结构清晰,分点说明,并引用提供的参考资料来支持观点。同,要使用中文,符合用户的要求,并且按照系统级指令正确使用数学公式的格式,不过这个问题可能不太涉及数学表达式,所以可能不需要用LaTeX数学格式,但如果有代码块的话,需要用正确的格式包裹。 最后,生成相关问题,可能需要考虑常见的疑惑点,比如为什么exports无效,如何正确导出多个方法,或者何选择使用exports还是module.exports。这些问题可以帮助用户进一步理解主题。</think>### JavaScript中`exports`与`module.exports`的区别 在Node.js模块系统中,`exports``module.exports`都与模块导出相关,但它们的区别联系容易引发混淆。以下是关键点解析: --- #### 1. **初始关系** - **`exports`是`module.exports`的引用**:默认情况下,Node.js将`exports`变量初始化为`module.exports`的引用。此,通过`exports`添加属性等价于操作`module.exports`[^4]。 ```javascript // 初始状态 console.log(exports === module.exports); // true ``` --- #### 2. **赋值操作的影响** - **若直接给`exports`赋值新对象**:会切断`exports`与`module.exports`的引用关系。此后通过`exports`添加的属性将无法导出,因为模块最终导出的是`module.exports`[^1][^4]。 ```javascript // a.js exports = { name: "Alice" }; // 此exports不再指向module.exports // b.js const a = require('./a.js'); console.log(a); // {} (module.exports仍为空对象) ``` - **若直接操作`module.exports`**:始终能正确导出内容。 ```javascript // 正确导出对象 module.exports = { name: "Bob" }; ``` --- #### 3. **导出结果的优先级** - **模块最终导出的是`module.exports`**:无论是否修改`exports`,只有`module.exports`的内容会被返回给`require()`调用[^2]。 ```javascript // 示例:混合使用(错误) exports.a = 1; module.exports = { b: 2 }; // 结果:{ b: 2 }(exports.a被覆盖) ``` --- #### 4. **常见用法对比** | 场景 | 使用`exports` | 使用`module.exports` | |--------------------------|---------------------------|----------------------------| | 导出多个属性/方法 | `exports.func = () => {}` | `module.exports.func = ...`| | 导出单一对象/构造函数 | 不适用 | `module.exports = Class` | | 动态导出 | 不适用 | 通过条件语句修改`module.exports` | --- #### 5. **最佳实践** 1. **避免混用两者**:若使用`module.exports = ...`,则不再通过`exports`添加属性。 2. **单一导出场景**:直接使用`module.exports`导出函数、类或对象。 3. **多属性导出场景**:使用`exports.xxx`或保持`module.exports`的引用一致性[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值