【Nodejs】838- Nodejs 模塊化你所需要知道的事

轉(zhuǎn)自:wxbaba
juejin.cn/post/6899752077807845383
前言
我們知道,Node.js是基于CommonJS規(guī)范進(jìn)行模塊化管理的,模塊化是面對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景不可或缺的工具,或許你經(jīng)常使用它,但卻從沒(méi)有系統(tǒng)的了解過(guò),所以今天我們來(lái)聊一聊Node.js模塊化你所需要知道的一些事兒,一探Node.js模塊化的面貌。
正文
在Node.js中,內(nèi)置了兩個(gè)模塊來(lái)進(jìn)行模塊化管理,這兩個(gè)模塊也是兩個(gè)我們非常熟悉的關(guān)鍵字:require和module。內(nèi)置意味著我們可以在全局范圍內(nèi)使用這兩個(gè)模塊,而無(wú)需像其他模塊一樣,需要先引用再使用。
無(wú)需?require('require')?or?require('module')
復(fù)制代碼
在Node.js中引用一個(gè)模塊并不是什么難事兒,很簡(jiǎn)單:
const?config?=?require('/path/to/file')
復(fù)制代碼
但實(shí)際上,這句簡(jiǎn)單的代碼執(zhí)行了一共五個(gè)步驟:
了解這五個(gè)步驟有助于我們了解Node.js模塊化的基本原理,也能讓我們甄別一些陷阱,讓我們簡(jiǎn)單概括下這五個(gè)步驟都做了什么:
Resolving: 找到待引用的目標(biāo)模塊,并生成絕對(duì)路徑。 Loading: 判斷待引用的模塊內(nèi)容是什么類型,它可能是 .json文件、.js文件或者.node文件。Wrapping: 顧名思義,包裝被引用的模塊。通過(guò)包裝,讓模塊具有私有作用域。 Evaluating: 被加載的模塊被真正的解析和處理執(zhí)行。 Caching: 緩存模塊,這讓我們?cè)谝胂嗤K時(shí),不用再重復(fù)上述步驟。
有些同學(xué)看完這五個(gè)步驟可能已經(jīng)心知肚明,對(duì)這些原理輕車熟路,有些同學(xué)心中可能產(chǎn)生了更多疑惑,無(wú)論如何,接下來(lái)的內(nèi)容會(huì)詳細(xì)解析上述的執(zhí)行步驟,希望能幫助大家答疑解惑 or 鞏固知識(shí)、查缺補(bǔ)漏。
By the way,如果有需要,可以和我一樣,構(gòu)建一個(gè)實(shí)驗(yàn)?zāi)夸?,跟著Demo進(jìn)行實(shí)驗(yàn)。
什么是模塊
想要了解模塊化,需要先直觀地看看模塊是什么。
我們知道在Node.js中,文件即模塊,剛剛提到了模塊可以是.js、.json或者.node文件,通過(guò)引用它們,可以獲取工具函數(shù)、變量、配置等等,但是它的具體結(jié)構(gòu)是怎樣呢?在命令行中簡(jiǎn)單執(zhí)行下面的命令就可以看到模塊,也就是module對(duì)象的結(jié)構(gòu):
~/learn-node?$?node
>?module
Module?{
??id:?'' ,
??exports:?{},
??parent:?undefined,
??filename:?null,
??loaded:?false,
??children:?[],
??paths:?[?...?]?}
復(fù)制代碼
可以看到模塊也就是一個(gè)普通對(duì)象,只不過(guò)結(jié)構(gòu)中有幾個(gè)特殊的屬性值,需要我們一一去理解,有些屬性,例如id、parent、filename、children甚至都無(wú)需解釋,通過(guò)字面意思就可以理解。
后續(xù)的內(nèi)容會(huì)幫助大家理解這些字段的意義和作用。
Resolving
大致了解了什么是模塊后,我們從第一個(gè)步驟Resolving開(kāi)始,了解模塊化原理,也就是Node.js如何尋找目標(biāo)模塊,并生成目標(biāo)模塊的絕對(duì)路徑。
那么什么我們剛剛要先打印module對(duì)象,先讓大家了解module的結(jié)構(gòu)呢?因?yàn)檫@里有兩個(gè)字段值id、paths和Resolving這個(gè)步驟息息相關(guān)。一起來(lái)看看吧。
首先是 id屬性:
每個(gè)module都有id屬性,通常這個(gè)屬性值是模塊的完整路徑,通過(guò)這個(gè)值Node.js可以標(biāo)識(shí)和定位模塊的所在位置。但是在這兒并沒(méi)有具體的模塊,我們只是在命令行中輸出了module的結(jié)構(gòu),所以為默認(rèn)的值(repl表示交互式解釋器)。
其次是 paths屬性:
這個(gè)paths屬性有什么作用呢?Node.js允許我們用多種方式來(lái)引用模塊,比如相對(duì)路徑、絕對(duì)路徑、預(yù)置路徑(馬上會(huì)解釋),假設(shè)我們需要引用一個(gè)叫做find-me的模塊,require如何幫助我們找到這個(gè)模塊呢?
require('find-me')
復(fù)制代碼
我們先打印看看paths中是什么內(nèi)容:
~/learn-node?$?node
>?module.paths
[?'/Users/samer/learn-node/repl/node_modules',
??'/Users/samer/learn-node/node_modules',
??'/Users/samer/node_modules',
??'/Users/node_modules',
??'/node_modules',
??'/Users/samer/.node_modules',
??'/Users/samer/.node_libraries',
??'/usr/local/Cellar/node/7.7.1/lib/node'?]
復(fù)制代碼
ok,其實(shí)就是一堆系統(tǒng)絕對(duì)路徑,這些路徑表示了所有目標(biāo)模塊可能出現(xiàn)的位置,并且它們是有序的,這意味著Node.js會(huì)按序查找paths中列出的所有路徑,如果找到這個(gè)模塊,就輸出該模塊的絕對(duì)路徑供后續(xù)使用。
現(xiàn)在我們知道Node.js會(huì)在這一堆目錄中查找module,嘗試執(zhí)行require('find-me')來(lái)查找find-me模塊,由于我們并沒(méi)有在任何目錄放置find-me模塊,所以Node.js在遍歷所有目錄之后并不能找到目標(biāo)模塊,因此報(bào)錯(cuò)Cannot find module 'find-me',這個(gè)錯(cuò)誤大家也許經(jīng)??吹剑?/p>
~/learn-node?$?node
>?require('find-me')
Error:?Cannot?find?module?'find-me'
????at?Function.Module._resolveFilename?(module.js:470:15)
????at?Function.Module._load?(module.js:418:25)
????at?Module.require?(module.js:498:17)
????at?require?(internal/module.js:20:19)
????at?repl:1:1
????at?ContextifyScript.Script.runInThisContext?(vm.js:23:33)
????at?REPLServer.defaultEval?(repl.js:336:29)
????at?bound?(domain.js:280:14)
????at?REPLServer.runBound?[as?eval]?(domain.js:293:12)
????at?REPLServer.onLine?(repl.js:533:10)
復(fù)制代碼
現(xiàn)在,可以嘗試把需要引用的find-me模塊放在上述的任意一個(gè)目錄下,在這里我們創(chuàng)建一個(gè)node_modules目錄,并創(chuàng)建find-me.js文件,讓Node.js能夠找到它:
~/learn-node?$?mkdir?node_modules?
~/learn-node?$?echo?"console.log('I?am?not?lost');"?>?node_modules/find-me.js
~/learn-node?$?node
>?require('find-me');
I?am?not?lost
{}
>
復(fù)制代碼
手動(dòng)創(chuàng)建了find-me.js文件后,Node.js果然找到了目標(biāo)模塊。當(dāng)然,當(dāng)Node.js本地的node_modules目錄中找到了find-me模塊,就不會(huì)再去后續(xù)的目錄中繼續(xù)尋找了。
有Node.js開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)會(huì)發(fā)現(xiàn)在引用模塊時(shí),不一定非得指定到準(zhǔn)確的文件,也可以通過(guò)引用目錄來(lái)完成對(duì)目標(biāo)模塊的引用,例如:
~/learn-node?$?mkdir?-p?node_modules/find-me
~/learn-node?$?echo?"console.log('Found?again.');"?>?node_modules/find-me/index.js
~/learn-node?$?node
>?require('find-me');
Found?again.
{}
>
復(fù)制代碼
find-me目錄下的index.js文件會(huì)被自動(dòng)引入。
當(dāng)然,這是有規(guī)則限制的,Node.js之所以能夠找到find-me目錄下的index.js文件,是因?yàn)槟J(rèn)的模塊引入規(guī)則是當(dāng)具體的文件名缺失時(shí)尋找index.js文件。我們也可以更改引入規(guī)則(通過(guò)修改package.json),比如把index \-> main:
~/learn-node?$?echo?"console.log('I?rule');"?>?node_modules/find-me/main.js
~/learn-node?$?echo?'{?"name":?"find-me-folder",?"main":?"main.js"?}'?>?node_modules/find-me/package.json
~/learn-node?$?node
>?require('find-me');
I?rule
{}
>
復(fù)制代碼
require.resolve
如果你只想要在項(xiàng)目中引入某個(gè)模塊,而不想立即執(zhí)行它,可以使用require.resolve方法,它和require方法功能相似,只是并不會(huì)執(zhí)行被引入的模塊方法:
>?require.resolve('find-me');
'/Users/samer/learn-node/node_modules/find-me/start.js'
>?require.resolve('not-there');
Error:?Cannot?find?module?'not-there'
????at?Function.Module._resolveFilename?(module.js:470:15)
????at?Function.resolve?(internal/module.js:27:19)
????at?repl:1:9
????at?ContextifyScript.Script.runInThisContext?(vm.js:23:33)
????at?REPLServer.defaultEval?(repl.js:336:29)
????at?bound?(domain.js:280:14)
????at?REPLServer.runBound?[as?eval]?(domain.js:293:12)
????at?REPLServer.onLine?(repl.js:533:10)
????at?emitOne?(events.js:101:20)
????at?REPLServer.emit?(events.js:191:7)
>
復(fù)制代碼
可以看到,如果該模塊被找到了,Node.js會(huì)打印模塊的完整路徑,如果未找到,就報(bào)錯(cuò)。
了解了Node.js是如何尋找模塊之后,來(lái)看看Node.js是如何加載模塊的。
模塊間的父子依賴關(guān)系
我們把模塊間引用關(guān)系,表示為父子依賴關(guān)系。
簡(jiǎn)單創(chuàng)建一個(gè)lib/util.js文件,添加一行console.log語(yǔ)句,標(biāo)識(shí)這是一個(gè)被引用的子模塊。
~/learn-node?$?mkdir?lib
~/learn-node?$?echo?"console.log('In?util');"?>?lib/util.js
復(fù)制代碼
在index.js也輸入一行console.log語(yǔ)句,標(biāo)識(shí)這是一個(gè)父模塊,并引用剛剛創(chuàng)建的lib/util.js作為子模塊。
~/learn-node?$?echo?"require('./lib/util');?console.log('In?index,?parent',?module);"?>?index.js
復(fù)制代碼
執(zhí)行index.js,看看它們間的依賴關(guān)系:
~/learn-node?$?node?index.js
In?util
In?index??Module?{
??id:?'.',
??path:?'/Users/samer/',
??exports:?{},
??parent:?null,
??filename:?'/Users/samer/index.js',
??loaded:?false,
??children:?[
????Module?{
??????id:?'/Users/samer/lib/util.js',
??????path:?'/Users/samer/lib',
??????exports:?{},
??????parent:?[Circular?*1],
??????filename:?'/Users/samer/lib/util.js',
??????loaded:?true,
??????children:?[],
??????paths:?[Array]
????}
??],
??paths:?[...]
}
復(fù)制代碼
在這里我們關(guān)注與依賴關(guān)系相關(guān)的兩個(gè)屬性:children和parent。
在打印的結(jié)果中,children字段包含了被引入的util.js模塊,這表明了util.js是index.js所依賴的子模塊。
但仔細(xì)觀察util.js模塊的parent屬性,發(fā)現(xiàn)這里出現(xiàn)了Circular這個(gè)值,原因是當(dāng)我們打印模塊信息時(shí),產(chǎn)生了循環(huán)的依賴關(guān)系,在子模塊信息中打印父模塊信息,又要在父模塊信息中打印子模塊信息,所以Node.js簡(jiǎn)單地將它處理標(biāo)記為Circular。
為什么需要了解父子依賴關(guān)系呢?因?yàn)檫@關(guān)系到Node.js是如何處理循環(huán)依賴關(guān)系的,后續(xù)會(huì)詳細(xì)描述。
在看循環(huán)依賴關(guān)系的處理問(wèn)題之前,我們需要先了解兩個(gè)關(guān)鍵的概念:exports和module.exports。
exports, module.exports
exports:
exports是一個(gè)特殊的對(duì)象,它在Node.js中可以無(wú)需聲明,作為全局變量直接使用。它實(shí)際上是module.exports的引用,通過(guò)修改exports可以達(dá)到修改module.exports的目的。
exports也是剛剛打印的module結(jié)構(gòu)中的一個(gè)屬性值,但是剛剛打印出來(lái)的值都是空對(duì)象,因?yàn)槲覀儾](méi)有在文件中對(duì)它進(jìn)行操作,現(xiàn)在我們可以嘗試簡(jiǎn)單地為它賦值:
//?在lib/util.js的開(kāi)頭新增一行
exports.id?=?'lib/util';
//?在index.js的開(kāi)頭新增一行
exports.id?=?'index';
復(fù)制代碼
執(zhí)行index.js:
~/learn-node?$?node?index.js
In?index?Module?{
??id:?'.',
??exports:?{?id:?'index'?},
??loaded:?false,
??...?}
In?util?Module?{
??id:?'/Users/samer/learn-node/lib/util.js',
??exports:?{?id:?'lib/util'?},
??parent:
???Module?{
?????id:?'.',
?????exports:?{?id:?'index'?},
?????loaded:?false,
?????...?},
??loaded:?false,
??...?}
復(fù)制代碼
可以看到剛剛添加的兩個(gè)id屬性被成功添加到exports對(duì)象中。我們也可以添加除id以外的任意屬性,就像操作普通對(duì)象一樣,當(dāng)然也可以把exports變成一個(gè)function,例如:
exports?=?function()?{}
復(fù)制代碼
module.exports:
module.exports對(duì)象其實(shí)就是我們最終通過(guò)require所得到的東西。我們?cè)诰帉?xiě)一個(gè)模塊時(shí),最終給module.exports賦什么值,其他人引用該模塊時(shí)就能得到什么值。例如,結(jié)合剛剛對(duì)lib/util的操作:
const?util?=?require('./lib/util');
console.log('UTIL:',?util);
//?輸出結(jié)果
UTIL:?{?id:?'lib/util'?}
復(fù)制代碼
由于我們剛剛通過(guò)exports對(duì)象為module.exports賦值{id: 'lib/util'},因此require的結(jié)果就相應(yīng)地發(fā)生了變化。
現(xiàn)在我們大致了解了exports和module.exports都是什么,但是有一個(gè)小細(xì)節(jié)需要注意,那就是Node.js的模塊加載是個(gè)同步的過(guò)程。
我們回過(guò)頭來(lái)看看module結(jié)構(gòu)中的loaded屬性,這個(gè)屬性標(biāo)識(shí)這個(gè)模塊是否被加載完成,通過(guò)這個(gè)屬性就能簡(jiǎn)單驗(yàn)證Node.js模塊加載的同步性。
當(dāng)模塊被加載完成后,loaded值應(yīng)該為true。但到目前為止每次我們打印module時(shí),它的狀態(tài)都是false,這其實(shí)正是因?yàn)樵?code style="">Node.js中,模塊的加載是同步的,當(dāng)我們還未完成加載的動(dòng)作(加載的動(dòng)作包括對(duì)module進(jìn)行標(biāo)記,包括標(biāo)記loaded屬性),因此打印出的結(jié)果就是默認(rèn)的loaded: false。
我們用setImmediate來(lái)幫助我們驗(yàn)證這個(gè)信息:
//?In?index.js
setImmediate(()?=>?{
??console.log('The?index.js?module?object?is?now?loaded!',?module)
});
復(fù)制代碼
The?index.js?module?object?is?now?loaded!?Module?{
??id:?'.',
??exports:?[Function],
??parent:?null,
??filename:?'/Users/samer/learn-node/index.js',
??loaded:?true,
??children:
???[?Module?{
???????id:?'/Users/samer/learn-node/lib/util.js',
???????exports:?[Object],
???????parent:?[Circular],
???????filename:?'/Users/samer/learn-node/lib/util.js',
???????loaded:?true,
???????children:?[],
???????paths:?[Object]?}?],
??paths:
???[?'/Users/samer/learn-node/node_modules',
?????'/Users/samer/node_modules',
?????'/Users/node_modules',
?????'/node_modules'?]?}
復(fù)制代碼
ok,由于console.log被后置到加載完成(打完標(biāo)記)之后,因此現(xiàn)在加載狀態(tài)變成了loaded: true。這充分驗(yàn)證了Node.js模塊加載是一個(gè)同步過(guò)程。
了解了exports、module.exports以及模塊加載的同步性后,來(lái)看看Node.js是如何處理模塊的循環(huán)依賴關(guān)系。
模塊循環(huán)依賴
在上述內(nèi)容中,我們了解到了模塊之間是存在父子依賴關(guān)系的,那如果模塊之間產(chǎn)生了循環(huán)的依賴關(guān)系,Node.js會(huì)怎么處理呢?假設(shè)有兩個(gè)模塊,分別為module1.js和modole2.js,并且它們互相引用了對(duì)方,如下:
//?lib/module1.js
exports.a?=?1;
require('./module2');?//?在這兒引用
exports.b?=?2;
exports.c?=?3;
//?lib/module2.js
const?Module1?=?require('./module1');
console.log('Module1?is?partially?loaded?here',?Module1);?//?引用module1并打印它
復(fù)制代碼
嘗試運(yùn)行module1.js,可以看到輸出結(jié)果:
~/learn-node?$?node?lib/module1.js
Module1?is?partially?loaded?here?{?a:?1?}
復(fù)制代碼
結(jié)果中只輸出了{a: 1},而{b: 2, c: 3}卻不見(jiàn)了。仔細(xì)觀察module1.js,發(fā)現(xiàn)我們?cè)?code style="">module1.js的中間位置添加了對(duì)module2.js的引用,也就是exports.b = 2和exports.c = 3還未執(zhí)行之前的位置。如果我們把這個(gè)位置稱作發(fā)生循環(huán)依賴的位置,那么我們得到的結(jié)果就是在循環(huán)依賴發(fā)生前被導(dǎo)出的屬性,這也是基于我們上述驗(yàn)證過(guò)的Node.js的模塊加載是同步過(guò)程的結(jié)論。
Node.js就是這樣簡(jiǎn)單地處理循環(huán)依賴。在加載模塊的過(guò)程中,會(huì)逐步構(gòu)建exports對(duì)象,為exports賦值。如果我們?cè)谀K被完全加載前就引用這個(gè)模塊,那么我們只能得到部分的exports對(duì)象屬性。
.json和.node
在Node.js中,我們不僅能用require來(lái)引用JavaScript文件,還能用于引用JSON或C++插件(.json和.node文件)。我們甚至都不需要顯式地聲明對(duì)應(yīng)的文件后綴。
在命令行中也可以看到require所支持的文件類型:
~?%?node
>?require.extensions
[Object:?null?prototype]?{
??'.js':?[Function?(anonymous)],
??'.json':?[Function?(anonymous)],
??'.node':?[Function?(anonymous)]
}
復(fù)制代碼
當(dāng)我們用require引用一個(gè)模塊,首先Node.js會(huì)去匹配是否有.js文件,如果沒(méi)有找到,再去匹配.json文件,如果還沒(méi)找到,最后再嘗試匹配.node文件。但是通常情況下,為了避免混淆和引用意圖不明,可以遵循在引用.json或.node文件時(shí)顯式地指定后綴,引用.js時(shí)省略后綴(可選,或都加上后綴)。
.json文件:
引用.json文件很常用,例如一些項(xiàng)目中的靜態(tài)配置,使用.json文件來(lái)存儲(chǔ)更便于管理,例如:
{
??"host":?"localhost",
??"port":?8080
}
復(fù)制代碼
引用它或使用它都很簡(jiǎn)單:
const?{?host,?port?}?=?require('./config');
console.log(`Server?will?run?at?http://${host}:${port}`)
復(fù)制代碼
輸出如下:
Server?will?run?at?http://localhost:8080
復(fù)制代碼
.node文件:
.node文件是由C++文件轉(zhuǎn)化而來(lái),官網(wǎng)提供了一個(gè)簡(jiǎn)單的由C++實(shí)現(xiàn)的 hello插件 ,它暴露了一個(gè)hello()方法,輸出字符串world。有需要的話,可以跳轉(zhuǎn)鏈接做更多了解并進(jìn)行實(shí)驗(yàn)。
我們可以通過(guò)node-gyp來(lái)將.cc文件編譯和構(gòu)建成.node文件,過(guò)程也非常簡(jiǎn)單,只需要配置一個(gè)binding.gyp文件即可。這里不詳細(xì)闡述,只需要知道生成.node文件后,就可以正常地引用該文件,并使用其中的方法。
例如,將hello()轉(zhuǎn)化生成addon.node文件后,引用并使用它:
const?addon?=?require('./addon');
console.log(addon.hello());
復(fù)制代碼
Wrapping
其實(shí)在上述內(nèi)容中,我們闡述了在Node.js中引用一個(gè)模塊的前兩個(gè)步驟Resolving和Loading,它們分別解決了模塊的路徑和加載的問(wèn)題。接下來(lái)看看Wrapping都做了什么。
Wrapping就是包裝,包裝的對(duì)象就是所有我們?cè)谀K中寫(xiě)的代碼。也就是我們引用模塊時(shí),其實(shí)經(jīng)歷了一層『透明』的包裝。
要了解這個(gè)包裝過(guò)程,首先要理解exports和module.exports之間的區(qū)別。
exports是對(duì)module.exports的引用,我們可以在模塊中使用exports來(lái)導(dǎo)出屬性,但是不能直接替換它。例如:
exports.id?=?42;?//?ok,此時(shí)exports指向module.exports,相當(dāng)于修改了module.exports.
exports?=?{?id:?42?};?//?無(wú)用,只是將它指向了{(lán)?id:?42?}對(duì)象而已,對(duì)module.exports不會(huì)產(chǎn)生實(shí)際改變.
module.exports?=?{?id:?42?};?//?ok,直接操作module.exports.
復(fù)制代碼
大家也許會(huì)有疑惑,為什么這個(gè)exports對(duì)象似乎對(duì)每個(gè)模塊來(lái)說(shuō)都是一個(gè)全局對(duì)象,但是它又能夠區(qū)分導(dǎo)出的對(duì)象是來(lái)自于哪個(gè)模塊,這是怎么做到的。
在了解包裝(Wrapping)過(guò)程之前,來(lái)看一個(gè)小例子:
//?In?a.js
var?value?=?'global'
//?In?b.js
console.log(value)?//?輸出:global
//?In?c.js
console.log(value)?//?輸出:global
//?In?index.html
...
復(fù)制代碼
當(dāng)我們?cè)?code style="">a.js腳本中定義一個(gè)值value,這個(gè)值是全局可見(jiàn)的,后續(xù)引入的b.js和c.js都是可以訪問(wèn)該value值。但是在Node.js模塊中卻并不是這樣,在一個(gè)模塊中定義的變量具有私有作用域,在其它模塊中無(wú)法直接訪問(wèn)。這個(gè)私有作用域如何產(chǎn)生的?
答案很簡(jiǎn)單,是因?yàn)樵诰幾g模塊之前,Node.js將模塊中的內(nèi)容包裝在了一個(gè)function中,通過(guò)函數(shù)作用域?qū)崿F(xiàn)了私有作用域。
通過(guò)require('module').wrapper可以打印出wrapper屬性:
~?$?node
>?require('module').wrapper
[?'(function?(exports,?require,?module,?__filename,?__dirname)?{?',
??'\n});'?]
>
復(fù)制代碼
Node.js不會(huì)直接執(zhí)行文件中的任何代碼,但它會(huì)通過(guò)這個(gè)包裝后的function來(lái)執(zhí)行代碼,這讓我們的每個(gè)模塊都有了私有作用域,不會(huì)互相影響。
這個(gè)包裝函數(shù)有五個(gè)參數(shù):exports, require, module, __filename, __dirname。我們可以通過(guò)arguments參數(shù)直接訪問(wèn)和打印這些參數(shù):
~/learn-node?$?echo?"console.log(arguments)"?>?index.js
~/learn-node?$?node?index.js
{?'0':?{},
??'1':
???{?[Function:?require]
?????resolve:?[Function:?resolve],
?????main:
??????Module?{
????????id:?'.',
????????exports:?{},
????????parent:?null,
????????filename:?'/Users/samer/index.js',
????????loaded:?false,
????????children:?[],
????????paths:?[Object]?},
?????extensions:?{?...?},
?????cache:?{?'/Users/samer/index.js':?[Object]?}?},
??'2':
???Module?{
?????id:?'.',
?????exports:?{},
?????parent:?null,
?????filename:?'/Users/samer/index.js',
?????loaded:?false,
?????children:?[],
?????paths:?[?...?]?},
??'3':?'/Users/samer/index.js',
??'4':?'/Users/samer'?}
復(fù)制代碼
簡(jiǎn)單了解一下這幾個(gè)參數(shù),第一個(gè)參數(shù)exports初始時(shí)為空(未賦值),第二、三個(gè)參數(shù)require和module是和我們引用的模塊相關(guān)的實(shí)例,它們倆不是全局的。第四、五個(gè)參數(shù)__filename和__dirname分別表示了文件路徑和目錄。
整個(gè)包裝后的函數(shù)所做的事兒約等于:
function?(require,?module,?__filename,?__dirname)?{
??let?exports?=?module.exports;
??
??//?Your?Code...
??
??return?module.exports;
}
復(fù)制代碼
總而言之,wrapping就是將我們的模塊作用域私有化,以module.exports作為返回值將變量或方法暴露出來(lái),以供使用。
Cache
緩存很容易理解,通過(guò)一個(gè)案例來(lái)看看吧:
echo?'console.log(`log?something.`)'?>?index.js
//?In?node?repl
>?require('./index.js')
log?something.
{}
>?require('./index.js')
{}
>
復(fù)制代碼
可以看到,兩次引用同一個(gè)模塊,只打印了一次信息,這是因?yàn)榈诙我脮r(shí)取的是緩存,無(wú)需重新加載模塊。
打印require.cache可以看到當(dāng)前的緩存信息:
>?require.cache
[Object:?null?prototype]?{
??'/Users/samer/index.js':?Module?{
????id:?'/Users/samer/index.js',
????path:?'/Users/samer/',
????exports:?{},
????parent:?Module?{
??????id:?'' ,
??????path:?'.',
??????exports:?{},
??????parent:?undefined,
??????filename:?null,
??????loaded:?false,
??????children:?[Array],
??????paths:?[Array]
????},
????filename:?'/Users/samer/index.js',
????loaded:?true,
????children:?[],
????paths:?[
??????'/Users/samer/learn-node/repl/node_modules',
??????'/Users/samer/learn-node/node_modules',
??????'/Users/samer/node_modules',
??????'/Users/node_modules',
??????'/node_modules',
??????'/Users/samer/.node_modules',
??????'/Users/samer/.node_libraries',
??????'/usr/local/Cellar/node/7.7.1/lib/node'
????]
??}
}
復(fù)制代碼
可以看到剛剛引用的index.js文件處于緩存當(dāng)中,因此不會(huì)重新加載模塊。當(dāng)然我們也可以通過(guò)刪除require.cache來(lái)清空緩存內(nèi)容,達(dá)到重新加載的目的,這里不再演示。
總結(jié)
本文概述了使用Node.js模塊化時(shí)需要了解到的一些基本原理和常識(shí),希望幫助大家對(duì)Node.js模塊化有更清晰的認(rèn)識(shí)。但更深入的細(xì)節(jié)并未在本文中闡述,例如wrapper函數(shù)內(nèi)部的處理邏輯,CommonJS的同步加載的問(wèn)題、與ES模塊的區(qū)別等等。這些未提到的內(nèi)容大家可以在本文以外做更多探索。


【JS】646- 1.2w字 | 初中級(jí)前端 JavaScript 自測(cè)清單 - 1

【JS】676- 1.1w字 | 初中級(jí)前端 JavaScript 自測(cè)清單 - 2
回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 100+ 篇原創(chuàng)文章
