地址
說(shuō)明
模塊也算是nodejs的核心了佳遣。每個(gè)js文件就是一個(gè)模塊,通過(guò)exports對(duì)外開(kāi)放自己的方法税稼。正是因?yàn)橛辛薽odule的存在身害,才能讓nodejs實(shí)現(xiàn)模塊化。
功能模塊
- 訪問(wèn)主模塊(最開(kāi)始的調(diào)用者)
1.js require 2.js, 2.js require 3.js澈缺。 在3.js 里面可以通過(guò)訪問(wèn)require.main
來(lái)獲得1.js模塊坪创。
// 1.js
module.exports = {
a: 44
};
require('./2');
//3.js
console.log(require.main.exports);
// output: {a: 44}
這里需要注意的是,直接獲取的require.main
指的是這個(gè)模塊姐赡,并不是module.exports
對(duì)象莱预。所以想要獲取主模塊對(duì)外的屬性,可以通過(guò)require.main.exports
來(lái)實(shí)現(xiàn)项滑。所以require.main
指的是module
而不是module.exports
依沮。
- 循環(huán)依賴(lài)
記住一個(gè)原則: 當(dāng)require一個(gè)文件的時(shí)候,不管有沒(méi)有遇到循環(huán)依賴(lài)枪狂,你獲取到的這個(gè)模塊危喉,就是當(dāng)前的module.exports
。比如
// 1.js
console.log('1 starting');
exports.a= 1;
var b = require('./2.js');
exports.b= 2;
// 2.js
console.log('2 starting');
var b = require('./2.js');
console.log(b);
// output: {a: 1}
因?yàn)樵?.js在加載 2.js的過(guò)程中州疾,它是沒(méi)有完全執(zhí)行的(只有a屬性而沒(méi)有b屬性)辜限,所以在2.js獲取到的1.js,其實(shí)也就只有a屬性严蓖。等這些文件都完全加載完之后薄嫡,再更新require.cache
氧急。在此之前,都是半成品(雖然也會(huì)寫(xiě)入緩存)毫深。
緩存機(jī)制
上面說(shuō)得有點(diǎn)亂吩坝,現(xiàn)在再來(lái)理清一下緩存的機(jī)制。在剛加載這個(gè)文件的時(shí)候哑蔫,就已經(jīng)將這個(gè)模塊放在緩存require.cache
里面了钾恢,假如繼續(xù)有修改的話,會(huì)發(fā)現(xiàn)緩存也會(huì)接著變化鸳址。比如
console.log(require.cache); // exports = {};
module.exports.a = 2;
console.log(require.cache); // exports = {a: 2}
執(zhí)行上面這些語(yǔ)句的時(shí)候瘩蚪,可以對(duì)比一下兩次輸出,會(huì)發(fā)現(xiàn)緩存的對(duì)象已經(jīng)存在了稿黍,只是可能會(huì)接著發(fā)生變化而已疹瘦。下面有兩個(gè)問(wèn)題
如果有一個(gè)模塊未被完全加載,然后同時(shí)被另外的模塊加載巡球,那豈不是加載的數(shù)據(jù)不一樣啦言沐?
A:是的,比如上面那個(gè)循環(huán)依賴(lài)的例子加載一個(gè)模塊之后酣栈,可以通過(guò)修改它的數(shù)據(jù)來(lái)改變緩存嗎险胰,然后導(dǎo)致其他的調(diào)用者也發(fā)生變化嗎?
A:
var a = require('./other');
console.log(require.cache);
a.aaaaa = 33333;
console.log(require.cache);
通過(guò)上面簡(jiǎn)單地例子矿筝,我們可以看出起便,是的。會(huì)改變窖维。因?yàn)樵谶@里榆综,修改的是引用指向的內(nèi)存塊,所以能直接修改铸史。
文件模塊
在很多的nodejs開(kāi)源項(xiàng)目里面鼻疮,在某個(gè)文件夾下,會(huì)有個(gè)package.json文件琳轿。其中name
和main
是兩個(gè)比較重要的參數(shù)判沟。前者代表這個(gè)模塊的名字;后者表示崭篡,當(dāng)被加載的時(shí)候挪哄,去哪個(gè)地方尋找入口文件。比如在example文件下的
package.json
{
"name": "a6666",
"main": "./../test.js"
}
當(dāng)require('./example')的時(shí)候媚送,發(fā)現(xiàn)有個(gè)說(shuō)明文件中燥,然后根據(jù)
main查找入口文件寇甸。所以最終加載的是
test.js塘偎。
main`參數(shù)默認(rèn)是index.js疗涉。那么問(wèn)題來(lái)了,假如某個(gè)文件夾下有個(gè)example文件夾和同名文件example.js吟秩,會(huì)加載哪個(gè)呢咱扣?手動(dòng)試試就知道啦。我猜是加載目錄涵防。想要知道具體的原因的話闹伪,可以了解下模塊加載的順序。
加載node_modules的順序
從當(dāng)前的node_modules一直尋找壮池,找不到的話繼續(xù)在父目錄尋找偏瓤,直到找到為止,實(shí)在找不到就報(bào)錯(cuò)了椰憋。
這里有個(gè)全局模塊的概念厅克,會(huì)在環(huán)境變量中查找node_modules。不過(guò)官方并不推薦橙依,稱(chēng)之為“歷史原因”证舟,而且速度會(huì)降下來(lái)。所以建議是直接放在項(xiàng)目本地就好啦窗骑。
模塊包裝器
這是一個(gè)很重要的東西女责。因?yàn)橛羞@個(gè),才會(huì)有模塊化存在创译。每個(gè)文件其實(shí)是經(jīng)過(guò)包裝的(不然你以為那5個(gè)“全局”變量是怎么來(lái)的)
(function (exports, require, module, __filename, __dirname) {
// Your module code actually lives in here
});
這一層我們開(kāi)發(fā)者是感知不到的抵知,因?yàn)樵谶\(yùn)行時(shí)期才會(huì)進(jìn)行包裝。所以我們能直接使用這些變量软族。有了這個(gè)東東辛藻,想要污染全局變量都難啊
module的一些屬性
module.children
module.parent
上面這兩個(gè)屬性可以看出各個(gè)模塊的加載次序。一個(gè)模塊可以被加載多次互订,但是module.parent只有一個(gè)吱肌,隨之而來(lái)的是,一個(gè)模塊仰禽,只能是某一個(gè)模塊的children氮墨,而不能是多個(gè)。因?yàn)橥驴诘谝淮渭虞d的時(shí)候规揪,這些父子爺輩關(guān)系已經(jīng)明確下來(lái)了。無(wú)論后面怎么加載與被加載温峭,都不會(huì)發(fā)生變化猛铅。當(dāng)然,手動(dòng)修改緩存除外咯凤藏。module.loaded 是否曾經(jīng)被加載過(guò)
module.require(id) 返回緩存里面的 module.exports對(duì)象奸忽。
module.id 緩存的key(一般來(lái)說(shuō)文件實(shí)際路徑)
module.filename 緩存模塊的文件路徑
module.exports 用得最多的堕伪,就是這個(gè)家伙了。不解釋了栗菜。
小結(jié)
nodejs的模塊系統(tǒng)還是設(shè)計(jì)得挺好的欠雌,尤其是模塊化。比較有意思的是模塊加載機(jī)制疙筹。上面的 傳送門(mén) 有得看富俄,我也不解釋了(其實(shí)我沒(méi)深入看過(guò),哈哈)