NodeJs
的模塊化原文github
地址戳這裏: github.com/wangkaiwd/n…javascript
在介紹以前,咱們能夠先簡單瞭解一下javascript
的模塊化歷程前端
javascript
模塊化AMD
: Asynchronous Module DefinitionCMD
: Common Module DefinitionUMD
: Universal Module DefinitionCommonJS
: Node
採用該模塊化方式ES6
模塊化推薦文章:java
因爲這裏使用的是Nodejs
,因此咱們主要學習一些CMD(commonJS)
的相關內容node
NodeJs
中的模塊化require
和module.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.js
:github
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
的功能進行一個小結:
require
會引入一個模塊中經過module.exports
導出的元素require
首次引入模塊過程當中,會執行模塊文件中的代碼,並將模塊文件進行緩存exports
和module.exports
咱們先看一下NodeJs
官方對exports
的定義:
exports
變量是在模塊的文件級做用域內可用的,且在模塊執行以前賦值給module.expors
這句話的大概意思是說: exports
並非一個全局變量,只在模塊文件內有效,而且在每一個模塊文件(js
文件)執行以前將module.exports
的值賦值給exports
。即至關於在每一個js
文件的開頭執行了以下代碼:
exports = module.exports
複製代碼
這意味着exports
和module.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' }
複製代碼
因此咱們能夠得出如下執行過程:
node demo05.js
demo06.js
,而且執行demo06.js
,經過變量demo6
來接收模塊demo06.js
經過module.exports
導出的對象demo06.js
的過程當中,又引入了demo05.js
,而因爲demo05.js
已經執行了一部分,因爲緩存緣由,並不會從新執行,此時demo05.js
中的module.exports
仍是初始值{}
。因此變量demo5
爲{}
。demo05.js
在引入demo06.js
後繼續執行後續代碼能夠看出nodeJs
對於模塊之間的遞歸引用進行了優化,並不會引起死循環,可是須要注意的是在引入的時候要注意代碼的執行順序,不然可能會取不到對應的變量。
到這裏,小夥伴在NodeJs
中使用require
進行引入以及經過module.exports
來導出文件時的執行邏輯有了更清晰的認識呢?