一、CommonJS 模塊規(guī)范
Node 應(yīng)用由模塊組成,采用 CommonJS 模塊規(guī)范。
每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數(shù)、類,都是私有的,對其他文件不可見。
CommonJS 規(guī)范規(guī)定,每個模塊內(nèi)部,module 變量代表當(dāng)前模塊。這個變量是一個對象,它的 exports 屬性(即 module.exports )是對外的接口。加載某個模塊,其實是加載該模塊的 module.exports 屬性。
CommonJS 規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。
CommonJS 模塊的特點如下:
- 所有代碼都運行在模塊作用域,不會污染全局作用域。
- 模塊可以多次加載,但是只會在第一次加載時運行一次,然后運行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。要想讓模塊再次運行,必須清除緩存。
- 模塊加載的順序,按照其在代碼中出現(xiàn)的順序。
1. module.exports 屬性
module.exports 屬性表示當(dāng)前模塊對外輸出的接口,其他文件加載該模塊,實際上就是讀取 module.exports 變量。
2. exports 變量
為了方便,Node 為每個模塊提供一個 exports 變量,它指向 module.exports。這等同于在每個模塊頭部,有一行這樣的命令:var exports = module.exports,我們可以通過以下方式來驗證
console.log(exports === module.exports); // true
所以在對外輸出模塊接口時,可以向 exports 對象添加屬性方法。
module.exports.age = 20
module.exports.getAge = function() {}
// 相當(dāng)于
exports.age = 20
exports.getAge = function() {}
但是不能直接將 exports 變量指向一個值,因為這樣等于切斷了 exports 與 module.exports 的聯(lián)系。
// 以下寫法無效,因為 exports 不再指向 module.exports 了。
exports = function() {};
3. module.exports 與 exports 的使用
當(dāng)一個模塊的對外接口,只是一個單一的值時,不能使用 exports 輸出,只能使用 module.exports 輸出。
// moduleA.js
// 1?? 正確 ?
module.exports = function() {};
// 2?? 錯誤 ?
exports = function() {};
導(dǎo)入模塊看結(jié)果:
// other.js
var moduleA = require('moduleA.js');
console.log(moduleA);
// 兩種寫法打印的值分別為:
// 1?? 預(yù)期結(jié)果 ? ? () { console.log('moduleD'); }
// 2?? 非預(yù)期結(jié)果 ? {}
分析結(jié)果:
首先我們要知道 module.exports 的初始值是 {},當(dāng)執(zhí)行 exports = function() {}; 賦值時,無論賦值的是基本數(shù)據(jù)類型還是引用數(shù)據(jù)類型,都將改變 exports 的指向,即切斷了 exports 與 module.exports 的聯(lián)系。但是我們模塊對外輸出的接口是 module.exports,所以 2?? 得到的是初始值 {}。
如果你覺得 exports 與 module.exports 之間的區(qū)別很難分清,一個簡單的處理方法,就是放棄使用 exports,只使用 module.exports。
*我個人也沒覺得 exports 的寫法有多方便,哈哈。
4. 總結(jié)
非常簡單,就三點:
-
module.exports初始值為一個空對象{}; -
exports是指向的module.exports的引用; -
require()返回的是module.exports而不是exports。
還是那句話,如果你覺得 exports 與 module.exports 之間的區(qū)別很難分清,一個簡單的處理方法,就是放棄使用 exports,只使用 module.exports。
二、require() 擴(kuò)展話題
以下案例源自知乎某帖回答,這里。
關(guān)于 require() 的解釋
To illustrate the behavior, imagine this hypothetical implementation of require(), which is quite similar to what is actually done by require():
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
注意實現(xiàn)順序,也就是下面代碼為什么不成功的原因。
// moduleA.js
module.exports = function() {};
// 為什么這段配置不成功?你們有 BUG!!!
exports.abc = 'abc';
require() 的時候,是先通過 exports.abc 獲取, 然后通過 module.exports 直接覆蓋了原有的 exports,所以 exports.abc = 'abc' 就無效了。
一般庫的封裝都是 exports = module.exports = _ (underscore 的例子)。
原因很簡單,通過 exports = module.exports 讓 exports 重新指向 module.exports。
三、References
The end.