一塊兒學習NodeJs的模塊化

從新理解NodeJs的模塊化

原文github地址戳這裏: github.com/wangkaiwd/n…javascript

在介紹以前,咱們能夠先簡單瞭解一下javascript的模塊化歷程前端

javascript模塊化

  • AMD : Asynchronous Module Definition
  • CMD : Common Module Definition
  • UMD : Universal Module Definition
  • CommonJS: Node採用該模塊化方式
  • ES6模塊化

推薦文章:java

因爲這裏使用的是Nodejs,因此咱們主要學習一些CMD(commonJS)的相關內容node

NodeJs中的模塊化

requiremodule.exports使用

NodeJs中,咱們會經過module.exports或者exports來導出一個javascript文件中定義的元素,而後經過require將導出元素進行引入:git

// demo02.js
console.log('1');
module.exports = () => {
  console.log('Hi, I am module demo2');
};
console.log('2');

// demo01.js
console.log('before require');
const demo2 = require('./demo02');
console.log('after require');
demo2();
複製代碼

接下來咱們在當前目錄中打開命令行窗口,輸入node demo02.jsgithub

before require
1
2
after require
Hi, I am module demo2
複製代碼

因此,咱們在經過require將一個模塊導入的時候,不只能夠接收模塊內部經過module.exports暴露的元素,還會執行相應模塊內的js代碼緩存

接下來,咱們在demo01.js中再加入如下代碼:bash

const repeatDemo2 = require('./demo02');
repeatDemo2();
複製代碼

執行後的輸出結果以下:模塊化

before require
1
2
after require
Hi, I am module demo2
Hi, I am module demo2
複製代碼

輸出結果大概告訴咱們這樣一件事: 在首次引入某個模塊的時候,NodeJs會對模塊內的代碼進行緩存,而當咱們再次引入該模塊時,會經過緩存來讀取導出的內容,模塊內的代碼並不會從新執行。函數

咱們能夠經過require.cache屬性來看到NodeJs對模塊的緩存:

// 在引入模塊以前和以後分別輸出require.cache
// demo03.js
console.log('before require');
console.log(require.cache);
const demo2 = require('./demo02');
console.log('after require');
console.log(require.cache);
複製代碼

經過截圖咱們能夠很明顯的看出,在require demo02後緩存中多了一些內容:

require.cache.png

在閱讀完上邊的代碼以後,這裏咱們能夠對require的功能進行一個小結:

  1. require會引入一個模塊中經過module.exports導出的元素
  2. require首次引入模塊過程當中,會執行模塊文件中的代碼,並將模塊文件進行緩存
  3. 當咱們再次引入該模塊的時候,會從緩存中讀取該模塊導出的元素,而不會再次運行該文件

exportsmodule.exports

咱們先看一下NodeJs官方對exports的定義:

exports變量是在模塊的文件級做用域內可用的,且在模塊執行以前賦值給module.expors

這句話的大概意思是說: exports並非一個全局變量,只在模塊文件內有效,而且在每一個模塊文件(js文件)執行以前將module.exports的值賦值給exports。即至關於在每一個js文件的開頭執行了以下代碼:

exports = module.exports
複製代碼

這意味着exportsmodule.exports指向了同一片內存空間,當爲exports或者module.exports從新賦值的時候,它們將再也不指向同一個引用,而咱們requie引入的一直都是module.exports導出的內容

// demo04.js
// 本質上來說:exports是module.exports的一個引用,它們指向同一片內存空間
// exports = module.exports
exports.a = 1;
module.exports = { b: 2 }; // 當引用發生變化的時候,exports再也不是module.exports的快捷方式
複製代碼

這時模塊暴露出來的對象是{b:2}

官方也對這種行爲進行了假設實現:

function require(/* ... */) {
  // 一個全局的module對象
  const module = { exports: {} };
  // 這裏自執行函數傳參時進行了賦值: exports = module.exports
  ((module, exports) => {
    // 模塊代碼在這。在這個例子中,定義了一個函數。
    function someFunc() {}
    exports = someFunc;
    // 此時,exports 再也不是一個 module.exports 的快捷方式,
    // 且這個模塊依然導出一個空的默認對象。
    module.exports = someFunc;
    // 此時,該模塊導出 someFunc,而不是默認對象。
  })(module, module.exports);
  // 最終導出的一直都是module.exports,只不過能夠經過exports來更改它們的引用,間接的改變module.exports
  return module.exports;
}
複製代碼

模塊之間的循環引用

假設咱們有這樣一種場景: 模塊a.js依賴於b.js中的某個方法,而模塊b.js也一樣依賴於a.js中的某個方法,這樣的話會不會形成死循環呢?

筆者這裏寫了一個demo來重現這個問題,幫助咱們更好的理解模塊之間的相互引用:

// demo05.js
const demo6 = require('./demo06');
console.log('I am demo5', demo6);
module.exports = { demo5: 'demo5' };

// demo06.js
const demo5 = require('./demo05');
console.log('I am demo6', demo5);
module.exports = { demo6: 'demo6' };
複製代碼

執行結果以下(咱們能夠先猜一下):

I am demo6 {}
I am demo5 { demo6: 'demo6' }
複製代碼

因此咱們能夠得出如下執行過程:

  1. 命令行執行node demo05.js
  2. 首先引入模塊demo06.js,而且執行demo06.js,經過變量demo6來接收模塊demo06.js經過module.exports導出的對象
  3. 在執行demo06.js的過程當中,又引入了demo05.js,而因爲demo05.js已經執行了一部分,因爲緩存緣由,並不會從新執行,此時demo05.js中的module.exports仍是初始值{}。因此變量demo5{}
  4. demo05.js在引入demo06.js後繼續執行後續代碼

能夠看出nodeJs對於模塊之間的遞歸引用進行了優化,並不會引起死循環,可是須要注意的是在引入的時候要注意代碼的執行順序,不然可能會取不到對應的變量。

到這裏,小夥伴在NodeJs中使用require進行引入以及經過module.exports來導出文件時的執行邏輯有了更清晰的認識呢?

相關文章
相關標籤/搜索