node.js 中 module.exports 及 exports 的差異及區別

先來看 module.exports。假設一個 node.js 模組名稱為 a.js,內容如下:

// a.js
module.exports = '123';

這個模組很單純,會回傳字串 '123'。如果在 b.js 中要調用,執行:

// b.js
var a = require('./a.js'); // a 為 123;

所以以白話講,module.exports 所被賦予的值,就會成為 require 之後的回傳值

exports vs module.exports

這兩者不同的地方,可以從 require 這個方法來看:

require 這個方法會實體化一個叫 Module 的 Class,並自我執行一個匿名函式。

// 這段示意 "require('./a.js')" 會做的事

// Module 的建構式,
// Module 擁有一些屬性,其中一個是 'exports'
function Module() {
  // other setup
  this.exports = {};
}

var module = new Module();
(function(exports, require, module){
  // a.js 的內容
  module.exports = '123';
})(module.exports, require, module);

注意到 a.js 的內容是在該自動執行的匿名函式中執行的,所以可以看到在 a.js 的 closure 中,會有三個變數 exportsrequiremodule,也就是在定義模組時很常用到的三個關鍵字。

從自動執行匿名函式丟入的參數 (module.exports, require, module) 可以知道 a.js closure 中的 exports,就等於 module.exports

因為 exports 指向 module.exports,所以要 expose 屬性就不必打 module.exports,簡化為 exports 就好:

// a.js
exports.name = 'jcc';
// 上述其實等於 module.exports = 'jcc';

但是如果在 a.js 中改變 exports 的指向,就會喪失對 module.exports 的指向。此時對 exports 下屬性就不會作用在 module.exports,這不會是你要的:

// a.js
exports = {}; // exports 指向到一個空物件
exports.value = 'a value';

// b.js
var a = require('./a.js');
console.log(a.value); // undefined

因此會有人說*「記得重新把 exports 指向 module.exports」*:

// a.js
exports = module.exports = {};
exports.value = 'a value';

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

上面範例強力參考了 stackoverflow 上的這個回答,如果這邊看不懂可以去看看原文。

無論如何,如果你覺得一直用 exports = module.exports 卻不懂背後原理的,希望可以幫助到你。

輸出 Class 及靜態方法

利用上面的方法就可以實作這個功能,先把 exports 重新指向到 module.exports 以及 Class 的建構式,接著再把靜態方法指定在 exports 變數上:

// personFactory.js
var counts = 0;
exports = module.exports = function(name){
  counts++;
  this.name = name;
  this.rocks = function() {
    console.log(this.name + ' rocks!');
  }
};

exports.counts = function() {
  console.log(counts + ' ppl is created');
};
// index.js
var PersonFactory = require('./personFactory');
var jcc = new PersonFactory('jcc');
jcc.rocks(); // log 'jcc rocks'
PersonFactory.counts() // log '1 ppl is created'

這邊需要了解一個情況:可以對函式附加屬性。所以透過這樣可以產生靜態方法。