在我們繼續之前,重要的是您要了解how modules are actually loaded by node。
採取從節點的模塊加載系統遠離關鍵的一點是,它實際上運行的代碼之前,你require
(這發生在Module#_compile
),它creates一個新的空exports
對象爲Module
的屬性。 (換句話說,你的可視化是正確的。)
節點然後wraps文本在require
d文件與匿名函數:
(function (exports, require, module, __filename, __dirname) {
// here goes what's in your js file
});
...,基本上eval
s表示字符串。的eval
荷蘭國際集團該字符串的結果是一個函數(匿名包裝之一),和節點立即調用的函數,傳遞中的參數是這樣的:
evaledFn(module.exports, require, module, filename, dirname);
require
,filename
,和dirname
是到參考require
函數(其實際isn't a global)和包含加載模塊的路徑信息的字符串。 (這是當他們說什麼docs意味着「__filename
並不是一個真正的全球性,而是本地的每一個模塊。」)
因此,我們可以看到,一個模塊內,確實exports === module.exports
。但爲什麼模塊同時得到module
和exports
?
向後兼容性。在節點的最初幾天,模塊內部沒有任何module
變量。您只需分配到exports
。但是,這意味着你永遠不能將構造函數作爲模塊本身導出。
模塊導出一個構造函數作爲模塊的熟悉的例子:
var express = require('express');
var app = express();
這工作,因爲Express exports a function by reassigning module.exports
,覆蓋空對象節點在默認情況下爲您提供:
module.exports = function() { ... }
然而,請注意,您不能只寫:
exports = function { ... }
T他是因爲JavaScript's parameter passing semantics are weird。從技術上講,JavaScript可能被認爲是「純粹按價值傳遞」,但實際上參數通過「按參考值」傳遞。
這意味着,當您將對象傳遞給函數時,它會將該對象的引用作爲值接收。您可以通過該引用訪問原始對象,但不能更改對引用的調用者的感知。換句話說,沒有像你在C/C++/C#中看到的「out」參數那樣的東西。
舉一個具體的例子:
var obj = { x: 1 };
function A(o) {
o.x = 2;
}
function B(o) {
o = { x: 2 };
}
調用A(obj);
將導致obj.x == 2
,因爲我們通過訪問作爲o
原始對象。但是,B(obj);
將無所作爲; obj.x == 1
。向B
的本地o
分配一個全新對象只會改變o
指向的內容。它對呼叫者的對象obj
沒有任何影響,它不受影響。
現在應該很明顯,爲什麼有必要將module
對象添加到節點模塊的本地範圍。爲了允許模塊完全替換exports
對象,它必須作爲對象傳遞給模塊匿名函數的屬性使用。顯然沒有人想破壞現有的代碼,因此exports
留作module.exports
的參考。
因此,當您只是將屬性分配給導出對象時,無論您使用的是exports
還是module.exports
;它們是同一個,指向完全相同的對象。
當你想給一個函數出口爲一體的頂級出口,你必須使用module.exports
,因爲正如我們看到的,只是分配功能exports
將有模塊範圍之外沒有影響它的唯一。
最後要注意,當您正在導出一個功能模塊,這是一個很好的做法,分配給都exports
和module.exports
。這樣,兩個變量保持一致並且與標準模塊中的變量一樣。
exports = module.exports = function() { ... }
一定要做到這一點靠近你的模塊文件的頂部,所以沒有人意外分配到exports
這最終得到覆蓋。
而且,如果看起來怪你( =
S'),我走的事實,即包含assignment operator表達式返回分配的值,這使得它可以在一臺值賦給優勢一次拍攝多個變量。
爲什麼'express'同時分配兩個變量的唯一原因實際上只是「良好實踐」或「以防萬一」?是否真的沒有其他原因,甚至沒有更好或甚至需要的附帶條件? – trysis