您好,登錄后才能下訂單哦!
怎么在Node中模塊規(guī)范CommonJS?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
1. 路徑分析
Node中的模塊分為核心模塊和文件模塊 。
核心模塊是由Node提供的模塊,它們在Node源代碼的編譯過程中就編譯進(jìn)了二進(jìn)制執(zhí)行文件,在Node進(jìn)程啟動時,核心模塊就被直接加載進(jìn)內(nèi)存中,所以在引用核心模塊時,文件定位和編譯執(zhí)行這兩個步驟可以省略,并且在路徑分析中優(yōu)先判斷,所以它的加載速度時最快的。通過require引用核心模塊時,直接引用即可。如 require('http')
文件模塊是用戶編寫的模塊,它是在運(yùn)行時動態(tài)加載的,需要完整的路徑分析,文件定位,編譯執(zhí)行的過程,所以它的速度比核心模塊慢。引用文件模塊的方式分為三種:
1.以.或..開始的相對路徑文件模塊。
2.以/開始的絕對路徑文件模塊。
3.非路徑形式的文件模塊(自定義模塊)。
1,2兩種方法用于引用用戶自己編寫的模塊,require會將路徑轉(zhuǎn)為真實(shí)路徑,并以真實(shí)路徑作為索引,將編譯執(zhí)行的結(jié)果(對象)存放在緩存中,由于指定了明確的文件位置,其加載速度慢于核心模塊,快于自定義模塊。第3中方式用于引用下載的第三方模塊,這類模塊的查找是最費(fèi)時的。這里有一個 模塊路徑 的概念。自定義模塊的查找速度慢的原因就在于此。
/** 通過以下代碼,可以看出模塊路徑的生成規(guī)則如下:當(dāng)前目錄下的node_modules目錄,父目錄下的node_modules目錄,沿路徑向上逐級遞歸,直到根目錄下的node_modules目錄。 */ //a.js console.log(module.paths) //將打印出如下結(jié)果 [ 'H:\\Files\\qiuzhao\\please-offer\\node_modules', 'H:\\Files\\qiuzhao\\node_modules', 'H:\\Files\\node_modules', 'H:\\node_modules' ]
1. require('../a.js')
2. require('/a.js')
3. require('koa')
2. 文件定位
1) 文件擴(kuò)展名:CommonJS規(guī)范允許在標(biāo)識符中不包含文件擴(kuò)展名,這時候Node會按照.js,.json,.node的次序補(bǔ)足擴(kuò)展名,依次嘗試。
2)目錄分析和包(自定義模塊):在分析提供給require的標(biāo)識符的過程中,在文件擴(kuò)展名的依次嘗試后,依然沒有得到對應(yīng)的文件,卻得到一個目錄,這在引用自定義模塊并沿著模塊路徑逐個進(jìn)行查找時經(jīng)常會出現(xiàn),此時Node會將目錄當(dāng)做一個包來處理。這種情況下,Node首先會在當(dāng)前目錄下查找package.json(包描述文件),通過JSON.parse()解析出對象后,從中取出main屬性指定的文件名進(jìn)行定位,視情況而定會j擴(kuò)展名的分析。如果main屬性指定的文件名錯誤或者根本就沒有package.json文件,Node會將index當(dāng)做默認(rèn)文件名,然后進(jìn)行擴(kuò)展名的依次嘗試。如果在目錄分析的過程中沒有成功定位到任何文件,則進(jìn)入模塊路徑的下一個路徑進(jìn)行查找,如果模塊路徑數(shù)組遍歷完畢仍未找到文件,則拋出錯誤。
3. 編譯執(zhí)行
在Node中,每個文件都是一個模塊,每個模塊都是一個對象,這個對象的定義如下:
function Module(id,parent){ this.id = id this.exports = {} this.parent = parent if(parent&&parent.children){ parent.push(this) } this.filename = null this.loaded = false this.children = [] //當(dāng)前模塊引用的其他模塊會存儲在這里 }
在成功定位到文件后,首先Node會 新建一個對象 ,然后會將文件內(nèi)容載入并編譯執(zhí)行,并 將模塊的exports屬性返回給調(diào)用方 。針對不同擴(kuò)展名的文件,有不同的載入方法,通過 require.extensions
可以查看系統(tǒng)以及支持的文件加載方式。
1).js文件:通過fs模塊 同步 讀取文件后編譯執(zhí)行。
在編譯該類型的文件時,Node會對獲取得文件內(nèi)容進(jìn)行頭尾的包裝,在頭部添加 (function(exports,require,module,__filename,__dirname){\n ,在尾部添加 \n}) 。一個正常的js文件會被包裝成如下的樣子:
(function(exports,require,module,__dirname,__filename){ ... }) //從這里可以看出,node對模塊的實(shí)現(xiàn),也借鑒了前端js經(jīng)常使用的利用函數(shù)作用域還形成一個獨(dú)立的空間,以防污染全局作用域,這里node包裝了這一過程。
包裝之后的代碼會通過vm原生模塊的runInThisContext()方法執(zhí)行(類似eval),返回一個具體的function對象(runInThisContext()的作用在這里就是聲明一個函數(shù)),最后,將當(dāng)前模塊對象(別忘了Node在成功定位到文件后,會首先創(chuàng)建一個module對象)的exports屬性,require方法,module本身,以及在之前兩步中得到的完整文件路徑和文件目錄作為參數(shù)傳遞給這個函數(shù)。這里有一點(diǎn)經(jīng)典的例子:
//當(dāng)我們想為模塊的輸出定義一個全新的對象時 //error exports = {} //right module.exports = {} //這樣做的原因時,exports和modlue.exports指向的是同一個對象,而exports={}這種方式,不會影響module.exports指向的對象。Node真正返回給調(diào)用者的是module.exports
var val = 10 var chageVal = function(val){ val = 100 console.log(val) } changeVal(val) //100 console.log(val) // /----------------------/ var obj = { age:12 } var changeName = function(obj){ obj = { age:21 } console.log(obj.age) } changeName(obj) //21 console.log(obj.age) //12 //出現(xiàn)這種現(xiàn)象的原因是,當(dāng)調(diào)用函數(shù)時,傳入的是變量的副本。
2).node文件:這是使用C/C++編寫的擴(kuò)展文件,通過dlopen()方法加載最后編譯執(zhí)行的結(jié)果。dlopen()方法在不同平臺下有不同的實(shí)現(xiàn),通過libuv兼容層封裝。實(shí)際上,.node的模塊文件不需要編譯,因?yàn)樗蔷帉慍/C++模塊之后編譯生成的,這里只有加載和執(zhí)行的過程,沒有編譯的過程。在執(zhí)行的過程中,模塊的exports對象與.node模塊產(chǎn)生聯(lián)系,然后返回給調(diào)用者。
3).json文件:通過fs模塊同步讀取文件后,使用JSON.parse()解析后返回結(jié)果。 這種類型的文件是三者中編譯最簡單的,Node利用fs模塊同步讀取文件內(nèi)容后,調(diào)用JSON.parse()將其解析成對象,然后將其賦值給模塊對象的exports,以供外部調(diào)用。
4).其他擴(kuò)展名文文件:被當(dāng)做.js文件進(jìn)行處理。
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。