module.exports屬性與exports變量的區(qū)別

一、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 變量指向一個值,因為這樣等于切斷了 exportsmodule.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 的指向,即切斷了 exportsmodule.exports 的聯(lián)系。但是我們模塊對外輸出的接口是 module.exports,所以 2?? 得到的是初始值 {}。

如果你覺得 exportsmodule.exports 之間的區(qū)別很難分清,一個簡單的處理方法,就是放棄使用 exports,只使用 module.exports

*我個人也沒覺得 exports 的寫法有多方便,哈哈。

4. 總結(jié)

非常簡單,就三點:

  • module.exports 初始值為一個空對象 {};
  • exports 是指向的 module.exports 的引用;
  • require() 返回的是 module.exports 而不是 exports。

還是那句話,如果你覺得 exportsmodule.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.exportsexports 重新指向 module.exports。

三、References

The end.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容