一、CommonJS特點
經過前面討論,已經知道無模組化時專案中存在的問題。CommonJS的特點就是解決這些問題即:
1.每個檔案都是一個單獨的模組,有自己的作用域,宣告的變數不是全域性變數(除非在模組內宣告的變數掛載到global上)
2.每個檔案中的成員都是私有的,對外不可見
3.A模組依賴B模組時,在A模組內部使用require函式引入B模組即可,模組之間依賴關係更加清晰
4.模組的載入有快取機制,當載入完一次後,後續再載入就會讀取快取中的內容
5.模組的載入順序是按照程式碼的書寫順序來載入
二、CommonJS的應用環境
應用在Node.js中。CommonJS的載入機制是同步的,在Node環境中模組檔案是存在本地硬碟中,所以載入起來比較快,不用考慮非同步模式。
三、CommonJS的用法
已經知到CommonJS規範下,每個檔案就是一個模組,都有私有作用域,那如何才能讓外部訪問某個模組中的內容呢?
方式1:把模組中的成員掛載到global全域性物件中 【非常不推薦】
方式2:使用模組的成員module.exports或者exports匯出成員
最後在外部使用require函式引入所需模組
方式1:把變數掛載到global模組下【看完忘掉即可】
//module-b.js
var a = 10;
global.a = a;
//module-a.js
require("./module-b.js");
console.log(a); //10
方式2:使用module.exports匯出成員
1、使用module.exports單個匯出
//module-b.js
const a = 10;
const add = function(a, b) {
return a + b;
}
module.exports.a = a;
module.exports.add = add;
//module-a.js
const moduleB = require("./module-b.js");
console.log(moduleB.a); //10
console.log(moduleB.add(1, 1)); //2
2、使用module.exports直接匯出一個物件
//module-b.js
const a = 10;
const add = function(a, b) {
return a + b;
}
module.exports={a,add};
//module-a.js
const moduleB = require("./module-b.js");
console.log(moduleB.a); //10
console.log(moduleB.add(1, 1)); //2
3、使用exports代替module.exports來匯出成員
//module-b.js
const a = 10;
const add = function(a, b) {
return a + b;
}
exports.a=a;
exports.add=add;
//使用exports時 不可使用下面這種方式匯出成員
exports={a,add}
//module-a.js
const moduleB = require("./module-b.js");
console.log(moduleB.a); //10
console.log(moduleB.add(1, 1)); //2
由程式碼可見,在使用module.exports和exports匯出成員時略有不同,具體是為什麼呢?稍後作出解釋
四、module.exports、exports和require為什麼可以直接使用?
從我們平時寫程式碼的經驗來看,在一個檔案中可以使用的成員由以下幾種情況:
1、全域性成員
2、在檔案內部宣告瞭該成員
但我們所瞭解的程式碼執行環境中的全域性成員只有一個,像window和global這種,那大概率不是全域性成員。而且我們在模組內部並未宣告這三個變數,那為何能直接使用呢?
其實在node執行環境中,每個模組都是執行在一個函式中,正是因為這個函式的存在,才讓每個模組有了私有作用域
(function (exports, require, module, __filename, __dirname) {
// HERE IS YOUR CODE
});
通過程式碼來證明一下這個函式的存在
既然我們寫的程式碼都在函式內部,那我們應該通過arguments能獲取到這個函式的引數
//module-a.js
console.log('模組中的第一句程式碼');
console.log(arguments.length)
//執行結果
模組中的第一句程式碼
5
arguments.length的值是5,那八成就是這個樣子了。但感覺說服力不強,繼續看...
//module-a.js
console.log('模組中的第一句程式碼');
console.log(arguments.callee.toString())
//執行結果
模組中的第一句程式碼
function (exports, require, module, __filename, __dirname) {
console.log('模組中的第一句程式碼');
console.log(arguments.callee.toString())
}
終於露出了廬山真面目,為什麼可以直接用,應該一目瞭然了!(可以嘗試列印一下這個五個引數中都是什麼內容)
五、module.exports和exports
通過列印module成員,可以看到exports是module下的一個物件。module的exports屬性表示當前模組對外輸出的橋樑,module.exports指向的成員都會被暴露出去
以上示例中的寫法都是把模組內的成員掛載到module.exports中暴露出去的
const a = 10;
const add = function(a, b) {
return a + b;
}
module.exports.a = a;
module.exports.add = add;
或者使用
module.exports={a,add}
exports又如何匯出的呢?
//module-a.js
console.log(module.exports)
console.log(exports)
//輸出結果
{}
{}
兩個成員都是物件,那會不會是同一個東西呢?
//module-a.js
console.log(module.exports)
console.log(exports)
console.log(module.exports===exports)
//輸出結果
{}
{}
true
可見兩個成員完全相等,則指向的堆記憶體的地址是同一個,所以使用exports匯出模組內的成員也是理所應當的了。
所以模組最外部函式應該是有這麼一句程式碼的
(function (exports, require, module, __filename, __dirname) {
exports=module.exports={}; //指向同一個記憶體地址
// HERE IS YOUR CODE
});
既然exports和module.exports是指向的是同一個記憶體,按說用法是一樣的,為什麼上邊使用exports匯出成員時,特意說明不可以使用exports直接匯出一個物件呢?不妨試一下:
//module-b.js
const a = 10;
const add = function(a, b) {
return a + b;
}
// module.exports = { a, add };
exports = { a, add};
const moduleB = require("./module-b.js");
console.log(moduleB)//{}
console.log(moduleB.a)//undefined
實驗得出,通過exports直接匯出一個物件時在外部並拿不到匯出的資料,為什麼呢?
看一下module.exports和exports在記憶體中的情況
當載入該模組時,執行完exports=module.exports={}後的記憶體情況
當執行完exports={a,add}時的記憶體情況
當exports={a, add}時,exports在記憶體中和module.exports指向的就不是同一個記憶體地址了,說白了抱不了module.exports的大腿了,我們們上面說過,模組匯出成員是通過module.exports匯出的。exports和module.exports不是同一個記憶體時,exports自然無法匯出成員了。
既然如此那就把匯出的成員老老實實掛載到exports下吧。整洋氣一些,module.exports和exports同時使用
//module-b.js
const a = 10;
const add = function(a, b) {
return a + b;
}
module.exports = add;
exports.a = a;
//module-a.js
const moduleB = require("./module-b.js");
console.log(moduleB)//Function
console.log(moduleB.a)//undefined
納尼???把匯出的成員掛載到exports下了為何引用的時候還是undefined???
注意:在使用exports.a=a前 使用了module.exports=add了,這時候使用exports為什麼導不出成員,大家應該都明白了【原因同上】
為了避免在匯出成員時,有這樣或那樣的問題,建議在模組中全部使用module.exports吧
六、require()
通過上面一系列的程式碼案例可以看出,require的作用是載入所依賴的檔案。說白了就是執行了所載入模組的最外層的函式。
//module-b.js
const a = 10;
console.log('module-b中列印', a)
module.exports = { a };
//module-a.js
const moduleB = require("./module-b.js");
console.log(moduleB);
執行module-a.js的結果:
module-b中列印 10
{ a: 10 }
module-b.js中的console.log執行了。可見require函式確實令模組最外部的函式執行了。
由require執行完後有個引數來接受返回值看出,模組最外部的函式執行完後是有返回值的,那麼模組最外部的函式應該是這個樣子:
(function (exports, require, module, __filename, __dirname) {
exports = module.exports = {}; //指向同一個記憶體地址
// HERE IS YOUR CODE
return module.exports;//把module.exports返回出去,同時module.exports下掛載的成員也返回出去了
});
1、require載入檔案的方式(字尾名預設是js)
(1)通過相對路徑載入模組,以"./"或"../"開頭。比如:require("./module-b")是載入同級目錄下的module-b.js
(2)通過絕對路徑載入模組,以"/"開頭,這時候會去磁碟碟符根目錄或者網站根目錄去找此模組。比如:require("/module-b"),此時 會去根目錄去找module-b.js
(3)直接通過模組名載入,比如:require("math"),這是載入提供node內建的核心模組或者node_modules目錄中安裝的模組。當加 載的是node_modules中的模組時,查詢規則如下
//檔案所在目錄 E:/Work/Module/CommonJs/module-a.js
const math = require("math");
查詢規則:【假設每次在對應的目錄下都沒找到math模組】
1、先去CommonJS目錄下的node_modules中去查詢math模組
2、再去Module資料夾下的node_modules中去查詢math模組
3、再去Work資料夾下node_modules中去查詢math模組
4、再去E盤下的node_modules中去查詢math模組
最頂級目錄還找不到的話則報錯...
2、模組的載入機制
CommonJS模組的載入機制是,引入的值是輸出的值的拷貝,一旦模組內的值匯出後,在外部如何改變都不會影響模組內部,同樣模組內部對這個值如何改變也不會影響模組外部,猶如“嫁出去的女兒,潑出去的水”