exports, module.exports和this 同时设置,最终导出的是什么

总有一些似乎没什么用但总是记不住的面试题,比如这道题:

// a.js
exports.c = 3;
module.exports = {
	a: 1,
	b: 2,
};
this.m = 5;

// b.js
const a = require('./a');
console.log(a);

这个模块导出什么?

如果只看上面代码中的 exportsmodule.exports,毫无疑问module.exports 的导出会生效。大概能猜出最后输出的是{ a: 1, b: 2}。不过,这个 this 又是啥?他们之间是怎么决定使用哪个的?

exports 、 module.exports 和 this 的区别

Node.js 中,exportsmodule.exports 都用于模块化导出,但它们有一些重要区别:

module.exports

module.exports是模块的输出。
使用 module.exports 实际上是在替换模块的导出对象,而不是添加属性到现有的对象。

// a.js
exports.c = 3;
module.exports = {
	a: 1,
	b: 2,
};

// b.js
const a = require('./a');
console.log(a); // { a: 1, b: 2}

即使换成下面代码,依旧是module.exports生效。

// a.js
module.exports = {
	a: 1,
	b: 2,
};
exports.c = 3;

所以在实践中,通常更推荐使用 module.exports 来保持一致性。

exports

exportsmodule.exports的一个引用,在模块加载时,require返回的是module.exports,而不是exports
使用 exports 时,只是在修改 module.exports 指向的对象,而不是替换它。

// a.js
exports.a = 1;
exports.b = 2;
exports.c = 3;

// b.js
const a = require('./a');
console.log(a); // { a: 1, b: 2, c: 3 }

也可以说,在不使用 module.exports 的情况下,exports.c = 3 实际上是相当于 module.exports.c = 3

this

this 在模块中指向 module.exports,因此你可以使用 this 来添加属性到模块的导出对象。

把上面的示例修改一下:

// a.js
exports.a = 1;
this.m = 5;

// b.js
const a = require('./a');
console.log(a); // { c: 3, m: 5 }

this.m = 5 实际上是在为 module.exports 添加一个属性 m,这相当于 module.exports.m = 5

在最开始的题目中,重赋值 module.exports 之后,this 也指向新的 module.exports。所以输出结果没有m

导入另一个模块时发生了什么

下面伪代码模拟了 Node.js 中 require 函数的实现,以帮助理解模块导入的过程。

function require(modulePath) {
	//1. 将modulePath转换为绝对路径:
	//2. 判断是否该模块已有缓存 (cache为require的一个属性)
	// if(require.cache["D:\\路径\\a.js"]){
	//   return require.cache["D:\\路径\\a.js"].result;
	// }

	//3. 读取文件内容
	//4. 包裹到一个函数中

	function __temp(module, exports, require, __dirname, __filename) {
		exports.c = 3;
		module.exports = {
			a: 1,
			b: 2,
		};
		this.m = 5;
	}

	//6. 创建module对象 (需调用函数)
	module.exports = {}; // 先创建空对象
	const exports = module.exports; // 把module.exports赋值给exports
	// 调用函数时,把module.exports当作this绑定
	__temp.call(module.exports, module, exports, require, module.path, module.filename);
	// 最终返回的是module.exports
	return module.exports;
}

require.cache = {};

在步骤 6 中,__temp.call(module.exports, module, exports, require, module.path, module.filename) 这行代码将 module.exports 作为 this 绑定到 __temp 函数中

// a.js
console.log(this === exports); // true
console.log(this === module.exports); // true

this, exports, module.exports 在一开始是一样的,都是同一个对象。

exports.c = 3; 相当于在对象中加入属性 c, 此时 this 也有 c
module.exports = { a: 1, b: 2}; 相当于把 this 指向新的对象,所以 this.m = 5; 没有生效。

输出过程展示:

// a.js
console.log(this === module.exports); //true
console.log(this === exports); //true
console.log(this); // {}
console.log(exports); // {}
console.log(module.exports); // {}
exports.c = 3;
module.exports = {
	a: 1,
	b: 2,
};
this.m = 5;

console.log(this === module.exports); // false
console.log(this === exports); // true
console.log(this); // { c: 3, m: 5 }
console.log(exports); // { c: 3, m: 5 }
console.log(module.exports); // { a: 1, b: 2 }

// 导出改模块,最终输出为 { a: 1, b: 2 }

总结

  • exportsmodule.exports 是用来定义模块的导出内容。exportsmodule.exports 的一个引用,但如果 module.exports 被重新赋值,exports 的引用将被忽略。

  • this 在模块文件的顶层上下文中,指向 module.exports。在模块开始时,this,exportsmodule.exports 是同一个对象的引用。但重新赋值 module.exports 时指向一个新的对象,exportsthis 仍然指向旧对象。

综合来说:

  1. module.exports:最终决定模块导出的内容。在这个示例中,它会被设置为 { a: 1, b: 2 }exports.cthis.m 都不会影响到最终的导出。

  2. exports.c = 3:不会生效,因为 module.exports 已经被重新赋值了。

  3. this.m = 5:也不会影响最终的导出,因为 module.exports 被重新赋值了。

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的解释,exports是module.exports的一个引用。当exports改变指向时,它就断绝了与module.exports的关联,无法正常导出成员。因此,当需要导出成员时,应该使用module.exports。exports只是module.exports的一个引用,不能独立地导出成员。 引用\[3\]进一步解释了module.exports与exports的区别。module.exports是整个js文件对外暴露的对象,可以包含任何类型的值。而exports只是指向module.exports的引用,相当于在js文件开头添加了一段代码var exports = module.exports。因此,当需要导出的成员是一个对象、字符串、数字或函数时,可以直接将其赋值给module.exports。而当需要导出的成员是一个单独的变量或函数时,可以将其赋值给exports对象的属性。 综上所述,当需要导出成员时,应该使用module.exports。而exports只是module.exports的一个引用,用于导出单独的变量或函数。 #### 引用[.reference_title] - *1* *2* [nodejs中使用exports与module.exports进行模块导出](https://blog.csdn.net/qq_42584511/article/details/98886933)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [(区别、详解、使用)module.exports与exports,exportexport default,import 与require](https://blog.csdn.net/qq_59747594/article/details/127700908)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值