exprots && module exports

艾倫先生發表於2017-12-14

在一個模組檔案中定義的本地(即非函式內部定義的)變數、函式或者物件都只在該模組內部有效,當你需要從模組外部引用這些變數、函式或者物件時,需要在該模組內部使用exports或者module.exports物件。

開發者可以在“全域性”環境下任意使用var申明變數(不用寫到閉包裡了),通過exports暴露介面給呼叫者。

是否糾結於什麼時候該用exports什麼時候該用module.exports呢?先簡要陳述一下兩者的區別,分別給出兩個例子後,在具體說一下使用的場景。

  • 對於要匯出的屬性,可以簡單直接掛到exports物件上

  • 對於類,為了直接使匯出的內容作為類的構造器可以讓呼叫者使用new操作符建立例項物件,應該把建構函式掛到module.exports物件上,不要和匯出屬性值混在一起

exports

測試一下

建立一個檔案作為我們自定義的格式化工具類,起名叫做format.js。在函式內我們定一個格式化金錢的函式formatMoney並使用exports對外匯出,再定義一個函式,不匯出

function formatMoney(s, n) {
  if (isNaN(s)) {
    return '--';
  }
  var negative = false;
  if (s < 0) {
    negative = true;
    s = Math.abs(s);
  }
  try {
    n = n >= 0 && n <= 20 ? n : 2;
    s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
    var l = s.split(".")[0].split("").reverse(), r = s.split(".")[1];
    t = "";
    for (i = 0; i < l.length; i++) {
      t += l[i] + ((i + 1) % 3 === 0 && (i + 1) !== l.length ? "," : "");
    }
    r = r ? '.' + r : '';
    var result = t.split("").reverse().join("") + r;
    if (negative) {
      return '-' + result;
    } else {
      return result;
    }

  }
  catch (e) {
    return '';
  }
}

function funcOnlyMe() {
    console.log('This function can not be used by other file')
}


exports.formatMoney = formatMoney;
複製程式碼

在同層的test.js檔案中引用模組檔案,呼叫模組暴漏給外部的方法和私有方法

var format = require('./format');

var moneyOutput1 = format.formatMoney('1982400');
console.log(moneyOutput1);
//1,982,400.00

format.funcOnlyMe();

// format.funcOnlyMe()
//        ^

// TypeError: format.funcOnlyMe is not a function
//     at Object.<anonymous> (/Users/beifeng/Desktop/test_node/test.js:6:8)
//     at Module._compile (module.js:398:26)
//     at Object.Module._extensions..js (module.js:405:10)
//     at Module.load (module.js:344:32)
//     at Function.Module._load (module.js:301:12)
//     at Function.Module.runMain (module.js:430:10)
//     at startup (node.js:141:18)
//     at node.js:1003:3
複製程式碼

module.exports

需要將模組定義為一個類的時候,需要使用module.exports的書寫方法。

將模組封裝成類的演示1

// module_test.js
var _name;
var name = '';

// 模組物件的建構函式
var foo = function(name) {
	_name = name;
}

//獲取私有變數 _name的變數值
foo.prototype.GetName = function() {
	return _name;
}

//設定私有變數 _name的變數值
foo.prototype.SetName = function(name) {
	_name = name;
}

foo.prototype.name = name;

module.exports = foo;

//main.js
var foo = require('./module_test');

var myFoo = new foo('feng');
console.log(myFoo.GetName());//feng

myFoo.SetName('liang');
console.log(myFoo.GetName());//liang
複製程式碼

封裝一個自定義的格式化價格的工具

module.exports = {
  formatMoney: function (s, n) {
    if (isNaN(s)) {
      return '--';
    }
    var negative = false;
    if (s < 0) {
      negative = true;
      s = Math.abs(s);
    }
    try {
      n = n >= 0 && n <= 20 ? n : 2;
      s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
      var l = s.split(".")[0].split("").reverse(), r = s.split(".")[1];
      t = "";
      for (i = 0; i < l.length; i++) {
        t += l[i] + ((i + 1) % 3 === 0 && (i + 1) !== l.length ? "," : "");
      }
      r = r ? '.' + r : '';
      var result = t.split("").reverse().join("") + r;
      if (negative) {
        return '-' + result;
      } else {
        return result;
      }

    }
    catch (e) {
      return '';
    }
  },
  formatWan: function (s, n) {
    s = s / 10000;
    n = n || 0;
    return this.formatMoney(s, n);
  },
  formatYi: function (s, n) {
    s = s / 100000000;
    n = n || 0;
    return this.formatMoney(s, n);
  },
  moneyUnit: function (money) {
    if (parseFloat(money) < 1) {
      return money * 100 + '分'
    } else if (parseInt(money) < 10000) {
      return money + '元'
    } else if (parseInt(money) < 100000000) {
      return this.formatWan(money, 2) + '萬元'
    } else {
      return this.formatYi(money, 2) + '億元'
    }
  }
}
複製程式碼

exports 和 module.exports區別

Node.js在模組編譯的過程中會對模組進行包裝,最終會返回類似下面的程式碼:

(function (exports, require, module, __filename, __dirname) {
    // module code...
});
複製程式碼

其中,module就是這個模組本身,require是對Node.js實現查詢模組的模組Module._load例項的引用,__filename和__dirname是Node.js在查詢該模組後找到的模組名稱和模組絕對路徑,這就是官方API裡頭這兩個全域性變數的來歷。

關於module.exports與exorts的區別,瞭解了下面幾點之後應該就完全明白:

模組內部大概是這樣:

exports = module.exports = {};
複製程式碼
  • exports是module.exports的一個引用。

  • require引用模組後,返回給呼叫者的是module.exports而不是exports

  • exports.xxx相當於在匯出物件上掛屬性,該屬性對呼叫模組直接可見

  • exports =相當於給exports物件重新賦值,呼叫模組不能訪問exports物件及其屬性

  • 如果此模組是一個類,就應該直接賦值module.exports,這樣呼叫者就是一個類構造器,可以直接new例項

也就是說,首先以下幾種匯出方式是一致的

exports.str = 'a';  
exports.func = function () {}; 

module.exports = {
	str : 'a',
	func : function(){}
}


exports = module.exports = {
	str : 'a',
	func : function(){}
}
複製程式碼

引用這個模組的檔案,看到的都是相同的內容

var foo = require('./module_test');

console.log(foo)
//{ str: 'a', func: [Function] }
複製程式碼

但是如果修改重寫exports屬性的話,情況就大不一樣了。在下面檔案中,我們先給exports物件設定兩個屬性,然後重寫exports屬性,最後再給exports物件設定兩個新屬性

exports.str1 = 'before overwrite exports'
exports.func1 = function () {
	console.log(this.str1);//此處的this代表當前匯出的exports物件
}; 
exports = {
	str2:'inner exports',
	func2:function(){}
}
exports.str3 = 'after overwrite';  
exports.func3 = function () {}; 
複製程式碼

引用這個模組的檔案

var foo = require('./module_test');

console.log(foo)
//{ str1: 'before overwrite exports', func1: [Function] }
複製程式碼

發現一個有趣的現象:重寫exports物件的一刻起,exports物件就失去了繼續對外匯出屬性的能力,外部檔案只能訪問重寫之前的exports物件屬性。

什麼時候使用exprots 什麼時候使用module.exports

有一點需要重申:exports只是module.exports的輔助方法。你的模組最終返回module.exports給呼叫者,而不是exportsexports所做的事情是收集屬性,如果module.exports當前沒有任何屬性的話,exports會把這些屬性賦予module.exports。如果module.exports已經存在一些屬性的話,那麼exports中所用的東西都會被忽略。

module.exports = 'hello world';
exports.name = function() {
    console.log('sorry , i am fail');
};
複製程式碼

引用這個模組的檔案

var foo = require('./module_test.js');
foo.name(); // TypeError: Object hello world has no method 'name'
複製程式碼

foo模組完全忽略了exports.name,然後返回了一個字串’hello world ’。通過上面的例子,你可能認識到你的模組不一定非得是模組例項。你的模組可以是任何合法的JavaScript物件 - boolean,number,date,JSON, string,function,array和其他。你的模組可以是任何你賦予module.exports的值。如果你沒有明確的給module.exports設定任何值,那麼exports中的屬性會被賦給module.exports中,然後並返回一個包含這些屬性的物件。

如果你的模組是一個類

module.exports = function(name, age) {
    this.name = name;
    this.age = age;
    this.about = function() {
        console.log(this.name +' is '+ this.age +' years old');
    };
};
複製程式碼

這樣呼叫它

var foo = require('./module_test.js');
var people = new foo('feng', 30);
people.about(); // feng is 26 years old
複製程式碼

在下面的情況下,你的模組是一個陣列:

module.exports = ['a', 'b', 'c', 'd', 'e'];

然後這樣呼叫

var foo = require('./module_test.js');
console.log('which one: ' + rocker[2]); //c
複製程式碼

所以:如果你想要你的模組成為一個特別的物件型別,那麼使用module.exports;如果你希望你的模組成為一個傳統的模組例項,使用exports,因為這貨基本上只能返回一個包含若干屬性的物件。

話雖如此,exports還是推薦的物件,除非你想把你模組的物件型別從傳統的模組例項修改為其他的型別的返回值。

希望對您有用~

相關文章