浅析Node模块中module.exports与exports的关系

280 阅读2分钟

Node.js遵循CommonJS规范的模块机制,一个JS文件即被视为一个独立的模块。在模块内部可以通过2种方式导出模块:

  1. module.exports
  2. exports

module.exports是一个对象引用,这个对象具有以下特征:

  1. 默认值:{}
  2. 模块始终导出该对象

exports也是一个对象引用,它与module.exports默认指向同一个对象

console.log(module.exports === exports);	// true

所以如果我们只是向module.exports(或exports)追加需要导出的内容时,采用哪种方式都可以,因为两者始终指向同一个对象

module.exports.a = 1;

console.log(module.exports);	// { a: 1 }
console.log(exports);			// { a: 1 }

exports.b = 2;
console.log(module.exports);	// { a: 1, b: 2 }
console.log(exports);			// { a: 1, b: 2 }

但如果给module.exports(或exports)重新赋值,那么就会出现一些意想不到的效果。假设我们使用如下的方式导出模块

// foo.js
module.exports = {
	a: 1
}
exports.b = 2;
// main.js
var foo = require('./foo');

console.log(foo);	// { a: 1 },导出的对象并不包含b

出现上面结果的原因是:module.exports在被’=’赋值时,实际上是新生成了一个{a: 1}对象,并且断开module.exports与默认对象{}的引用,从而指向{a: 1}这个新生成的对象;然而exports此时还是指向默认对象{}exports.b = 2;实际上是向这个默认对象添加了b,而不是module.exports所指的那个对象;并且Node模块最终只会导出module.exports所指向的那个对象(或值),所以就出现了上面的结果。

概括一句话就是:给模块对象的重新复制导致module.exports与exports分别指向了不同的对象

‘最好的证明就是用代码演示’,所以我们代码证明一下上面的分析

// foo.js
module.exports = {
  a: 1
}
exports.b = 2;

console.log(module.exports === exports);	// false
console.log(module.exports);				// { a: 1 }
console.log(exports);						// { b: 2 }
// main.js
var foo = require('./foo');

console.log(foo);	// { a: 1 }

那么是否有办法修补上面的问题呢?答案是:有的,问题的关键是:module.exports与exports指向了不同的对象,那么我们就将它们指回同一个对象

// foo.js
module.exports = {
  a: 1
}

console.log(module.exports === exports);	// false

exports = module.exports;
exports.b = 2;

console.log(module.exports === exports);	// true
console.log(module.exports);				// { a: 1, b: 2 }
console.log(exports);						// { a: 1, b: 2 }

所以,导出模块时我们最好采用如下形式:

exports = module.exports = {
	a: 1
}

console.log(module.exports === exports);	// true
console.log(module.exports);				// { a: 1 }
console.log(exports);						// { a: 1 }

以上就是对module.exports与exports两者关系的浅显分析,希望对大家有所帮助,如有错误,欢迎指正~

原文地址:www.guoyunfeng.com/2018/06/06/…