Node.js模塊里exports與module.exports的區別?


用一句話來說明就是,require方能看到的只有module.exports這個對象,它是看不到exports對象的,而我們在編寫模塊時用到的exports對象實際上只是對module.exports的引用。

如果你能理解上面這句話,那麼下面的都是廢話,可以不用看了,因為是用來解釋上面這句話的。

關於引用,可以用下面的例子來讓你搞得請清楚楚的:

首先說一個概念:

ECMAScript的變數值類型共有兩種:

基本類型 (primitive values) : 包括Undefined, Null, Boolean, Number和String五種基本數據類型;

引用類型 (reference values) : 保存在內存中的對象們,不能直接操作,只能通過保存在變數中的地址引用對其進行操作。

我們今天要討論的exports和module.exports屬於Object類型,屬於引用類型。

看下面的例子:

var module = {
exports:{
name:"我是module的exports屬性"
}
};
var exports = module.exports; //exports是對module.exports的引用,也就是exports現在指向的內存地址和module.exports指向的內存地址是一樣的

console.log(module.exports); // { name: "我是module的exports屬性" }
console.log(exports); // { name: "我是module的exports屬性" }

exports.name = "我想改一下名字";

console.log(module.exports); // { name: "我想改一下名字" }
console.log(exports); // { name: "我想改一下名字" }
//看到沒,引用的結果就是a和b都操作同一內存地址下的數據

//這個時候我在某個文件定義了一個想導出的模塊
var Circle = {
name:"我是一個圓",
func:function(x){
return x*x*3.14;
}
};

exports = Circle; // 看清楚了,Circle這個Object在內存中指向了新的地址,所以exports也指向了這個新的地址,和原來的地址沒有半毛錢關係了

console.log(module.exports); // { name: "我想改一下名字" }
console.log(exports); // { name: "我是一個圓", func: [Function] }

回到nodejs中,module.exports初始的時候置為{},exports也指向這個空對象。

那麼,這樣寫是沒問題的:

exports.name = function(x){
console.log(x);
};

//和下面這個一毛一樣,因為都是修改的同一內存地址里的東西

module.exports.name = function(x){
console.log(x);
};

但是這樣寫就有了區別了:

exports = function(x){
console.log(x);
};

//上面的 function是一塊新的內存地址,導致exports與module.exports不存在任何關係,而require方能看到的只有module.exports這個對象,看不到exports對象,所以這樣寫是導不出去的。

//下面的寫法是可以導出去的。說句題外話,module.exports除了導出對象,函數,還可以導出所有的類型,比如字元串、數值等。
module.exports = function(x){
console.log(x);
};

我講清楚了吧?


你改變不了 exports 的引用。準確來說,是改變後實際導出的還是 exports 原引用指向的對象。module.exports 就是用來修復此問題的。

如果我們把你的 JS 文件整個放在一個閉包內執行:

define(function(require, exports) {

exports = function() {};

});

exports 的引用改變外部根本觀察不到。但如果換成 module.exports:

define(function(require, exports, module) {

module.exports = function() {};

});

這時候 module 對內外的觀察者來說都是同一個東西,在內部改變了 module.exports 在外部能獲取到。


首先可以從文檔中看到 "exports" 的說明 Modules Node.js v0.10.33 Manual Documentation

exports alias#

The exports variable that is available within a module starts as a reference
to module.exports. As with any variable, if you assign a new value to it, it
is no longer bound to the previous value.

如果你一定要親眼所見,可以 debug 一下一個最簡單的 node.js 文件:

比如 test.js 里寫一行:

console.log("My Test");

我這用 node-inspector 調試他. 運行: node --debug-brk test.js

斷點設置在 "console.log("My Test");" 右面是當前調用棧. 可以看到 Node 在載入 .JS 時會把它封在一個 anonymous function 里. 這個 anonymous function 的參數就包括了. 這個 JS 能訪問到的 local var 其中也包括了 "exports" 和 "module"

接著往上調一個調用棧,看一下 Module._compile 是怎麼生成這些參數的:

這裡的 "var args" 就是所有參數. self 就是 module.

所以第一個參數 "exports" &<==&> 第三個參數 "module.exports"

(我的測試環境是: node v0.10.22)

exports 是 module.exports 的一個引用,如果 module.exports 改了. exports 就是失去意義了.

所以一直覺得: module.exports = exports = function () { }; 是比較好的習慣.

或者也可以無視 exports 的存在而直接用 module.exports.

具體你看 node 文檔里也提到了:

As a guideline, if the relationship between exports and module.exports
seems like magic to you, ignore exports and only use module.exports.


在node中,一個文件就是一個模塊。實際上,為了讓各個文件里的變數互不干擾,node讓每個模塊都放在一個閉包中執行,這樣實現的模塊的隔離。而要讓模塊間相互聯繫,就需要暴露變數。

node源碼裡面,定義了一個Module構造函數,每個模塊都是Module的實例,而exports則是Module的一個屬性。

function Module(id, parent) {
this.id = id;
this.exports = {};
...
}
module.exports = Module;

模塊的輸出就是exports。模塊想要對外暴露變數只需要對exports賦值。

// a.js

function foo() {
console.log("foo");
}

function bar() {
console.log("bar");
}

想要將這兩個函數暴露出去,可以直接使用exports

exports.foo = foo;
exports.bar = bar;

也可以對module.exports賦值

module.exports = {
foo: foo,
bar: bar
}

但是不能直接對exports賦值

// 錯誤
exports = {
foo: foo,
bar: bar
}

因為這樣做僅僅改變了exports的引用,而不改變module.exports。


這個東西其實類似:

var o = { a: 1},
b = o,
c = { b: 2};
b;// {a:1}
b = c;
b;//{b:2}
o;//{a:1}

賦值不會改變傳引用參數,即對b賦值,無論何值,o不受影響,但改變b的屬性,o就受影響了,exports和module.exports就這麼一個意思,對exports賦值,無法影響module.exports的值,但對http://exports.xxx賦值可以,對module.exports賦值,則可以覆蓋exports。因為exports是指向module.exports的。


exports=foo 直接賦值寫法會改變exports的引用,它就不再是原來在module上的exports了,這種情況就要用module.exports=foo 暴露變數


可以簡單理解為:

內部聲明了一個名為exports變數:var exports = module.exports;

最後暴露出去的是module.exports對象

所以可以通過http://exports.XXX來修改http://module.exports.XXX,但是不能通過exports = {}來修改module.exports;

舉個例子:

var wtf = {}; // 假裝它是module
wtf.exports = {}; // 假裝它是module.exports
var exports = wtf.exports; // 假裝它是exports

exports.a = "I am a"; // 修改 exports.a 相當於修改了 module.exports.a
console.log(wtf.exports.a) // "I am a"
exports = { b : "I am b" }; // 此時 exports 變數指向了一個新的對象,所以修改 module.exports 失敗!
console.log(wtf.exports.b) // undefined

其實可以用一個你自己喜歡的變數來代替exports:

舉個例子:

創建兩個文件:hello.js, world.js;

hello.js:

var wtf = module.exports;
wtf.hello = function() {console.log("hello, world")};

world.js:

var hello = require("./hello");
hello.hello(); // "hello, world"

然後運行node world.js;就會輸出』hello, world『;

exports和上面hello.js里的wtf沒什麼區別。


Module.exports和exports的區別


看了上面的答案,個人認識:

exports 就是 module.exports 的別名,是用來簡化書寫的,只能往下面掛載東西,直接改它沒意義。要麼就別用 exports 直接用 module.exports 好了。

如果理解不對請幫我指正,謝謝。


module.exports還是exports

我們首先通過一個例子來介紹exports的作用.首先新建一個模塊calc.js,代碼如下:

var add = function(a,b){
return a + b;
};

var minus = function(a,b){
return a - b;
};

再新建一個test.js文件,代碼如下 ,

var calc = require("./calc");

console.log(calc.add(1,2));

然後我們在Terminal中執行,發出現如下出錯.

/Users/Liuzc/Desktop/node/text.js:3

console.log(calc.add(1,2));

^

TypeError: Object #& has no method 『add』

at Object.& (/Users/Liuzc/Desktop/node/text.js:3:18)

at Module._compile (module.js:456:26)

at Object.Module._extensions..js (module.js:474:10)

at Module.load (module.js:356:32)

at Function.Module._load (module.js:312:12)

at Function.Module.runMain (module.js:497:10)

at startup (node.js:119:16)

at node.js:901:3

這時我們修改一下calc.js的代碼:

var add = function(a,b){
return a + b;
};

var minus = function(a,b){
return a - b;
};

exports.add = add;
exports.minus = minus;

再次在Terminal中執行node test.js時.正確的顯示了結果.

Liuzcs-MacBook-Pro:node Liuzc$ node text.js

3

一個模塊可以通過module.exports或exports將函數、變數等導出,以使其它JavaScript腳本通過require()函數引入並使用。

那麼,到底應該用module.exports還是用exports呢?我們先看下面的一個例子:

console.log(this);
console.log(exports);
console.log(module.exports);

console.log(this === exports);
console.log(this === module.exports);
console.log(exports === module.exports);

執行結果是:

{}

{}

{}

true

true

true

也就是說,exports默認和module.exports指向同一個空對象。

再看一個例子.calc.js中有代碼如下 :

exports.add = function(a,b){
return a + b;
}

module.exports.add = function(a,b){
return a - b;
}

這時猜猜執行test.js中的如下代碼的結果將會是什麼?

var calc = require("./calc");

console.log(calc.multiply(4,2));

對, 結果是2, 而不是8.也就是說如果運行時讓exports、this和module.exports指向不同的對象,只有module.exports指向的對象才回被導出。module.exports才是真正的介面,exports只不過是它的一個輔助工具。 最終返回給調用的是module.exports而不是exports。 所有的exports收集到的屬性和方法,都賦值給了module.exports。當然,這有個前提,就是module.exports本身不具備任何屬性和方法。如果,module.exports已經具備一些屬性和方法,那麼exports收集來的信息將被忽略。

如果你想你的模塊是一個特定的類型就用module.exports。如果你想的模塊是一個典型的「實例化對象」就用exports。

===========================================

之前學node.js時,這也是一個困惑過我的問題,以上分析是個人認為對這個問題解答的最清晰的(因為有例子)。

我就不無畫蛇添足了,直接從收藏夾中拿來。

感謝:http://liuzhichao.com/p/1669.html


exports為module.exports的引用,改變exports即exports=xxx僅僅改變exports的引用,不影響module.exports。但往exports上掛載任意屬性即exports.xxx=xxxx就等於往module.exports上掛載。

簡單的說就是,exports和module.exports是兩個指針指向了同一塊初始為空內存區域,改變exports的指向方向並不會改變module.exports指向內存塊的值。然後文件模塊本身是module對象,module.exports沒有值,require返回的也是空。

就是這樣,喵


推薦閱讀:

蘋果官網是怎麼做到完美保證多平台瀏覽體驗的?
你是如何構建 Web 前端 Mock Server 的?
有哪些函數式編程在前端的實踐經驗?
Vue.js 如何添加全局函數或變數?
什麼樣的前端技術 leader 是稱職的?

TAG:前端開發 | JavaScript | Nodejs | 前端工程師 | Express框架 |