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.
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.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}
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 #&
這時我們修改一下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);
執行結果是:
{}
{}{}truetruetrue
也就是說,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框架 |