原文:http://thenodeway.io/posts/how-require-actually-works/
介紹
掌握的基本知識(shí)
進(jìn)階
高級(jí)
幾乎所有的Node.js開(kāi)發(fā)者都可以說(shuō)出require()
的作用,但是又有多少人真正知道require()
是如何工作的呢深员。我們幾乎每天都會(huì)使用它去加載庫(kù)和模塊降淮,但是它的原理還是一個(gè)謎。
因?yàn)楹闷妫也榭戳薔ode的核心源碼去尋找答案钻注。但是我不是找到了一個(gè)函數(shù)片仿,而是找到了Node的核心模塊:module.js
。這個(gè)文件驚人的強(qiáng)大陆淀,它包含了文件的加載考余,編譯,并且可以緩存所有使用過(guò)的文件轧苫。對(duì)外使用的require()
只是冰山一角楚堤。
module.js
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// ...
module.js
里的模塊類型在Node.js中有兩個(gè)主要的作用。第一含懊,它為所有的Node.js模塊提供了一個(gè)函數(shù)用于編譯身冬。每一個(gè)文件在這個(gè)基礎(chǔ)模塊中運(yùn)行后都會(huì)返回一個(gè)新的實(shí)例,即使這個(gè)文件運(yùn)行了也還是會(huì)存在岔乔。這就是為什么我們可以隨時(shí)使用 module.exports
并且可以返回它酥筝。
這個(gè)模塊的第二個(gè)主要的作用就是管理Node模塊加載機(jī)制。這個(gè)獨(dú)立的require
函數(shù)其實(shí)是module.require
的一個(gè)引用雏门,而module.require
只是一個(gè)把Module._load
簡(jiǎn)單包裹了一下(wapper)嘿歌。這個(gè)函數(shù)才是真正控制文件的加載的,接下來(lái)我們通過(guò)這個(gè)函數(shù)繼續(xù)我們的探索剿配。
Module._load
Module._load = function(request, parent, isMain) {
// 1\. 檢查 Module._cache 是否有緩存
// 2\. 如果沒(méi)有緩存則創(chuàng)建一個(gè)新的模塊實(shí)例
// 3\. 將模塊實(shí)例保存到緩存中
// 4\. 通過(guò)給予的filename去調(diào)用module.load()搅幅,然后調(diào)用module.compile()去讀取文件內(nèi)容
// 5\. 如果文件的載入和解析過(guò)程中發(fā)生錯(cuò)誤,刪除緩存中的該模塊
// 6\. 返回 module.exports
};
Module._load
是一個(gè)負(fù)責(zé)新模塊的加載和管理模塊緩存的函數(shù)呼胚。緩存所有加載過(guò)的模塊可以減少文件的重復(fù)加載并且明顯地加快你的應(yīng)用茄唐。此外,共享模塊的實(shí)例可以把模塊像單例來(lái)使用,可以在整個(gè)項(xiàng)目的運(yùn)行中都可以保存它的狀態(tài)沪编。
如果一個(gè)模塊不存在在緩存中呼盆,Module._load
會(huì)為這個(gè)文件創(chuàng)建一個(gè)新的基礎(chǔ)模塊。Module._load
會(huì)通知模塊去讀取新的文件的內(nèi)容蚁廓,然后把內(nèi)容送到module._compile
访圃。[1]
如果你看了上面的#6,那么你就會(huì)看到module.exports
會(huì)被返回給用戶相嵌。這就是為什么你可以通過(guò)exports
和module.exports
創(chuàng)建一個(gè)對(duì)外的公共接口腿时,而這些就是Module._load
做的事情,然后通過(guò)require
返回出去饭宾。我很驚訝于除此之外沒(méi)有其他的神奇的地方了批糟,但是沒(méi)有什么比它更加簡(jiǎn)潔更加好的了。
module._compile
Module.prototype._compile = function(content, filename) {
// 1\. 創(chuàng)建一個(gè)獨(dú)立的require函數(shù)看铆,該函數(shù)可以調(diào)用module.require徽鼎。
// 2\. 給require加上其他幫助性的函數(shù)Attach other helper methods to require.
// 3\. 將代碼包裹在一個(gè)函數(shù)中,并提供了require弹惦,module等變量在模塊作用域中否淤。
// 4\. 運(yùn)行這個(gè)函數(shù)
};
這里就是見(jiàn)證奇跡的地方。第一棠隐,一個(gè)特殊的單獨(dú)的require
函數(shù)被創(chuàng)造用于這個(gè)模塊石抡。這個(gè)require
函數(shù)就是我們最熟悉的那個(gè)函數(shù)。這個(gè)函數(shù)只是把 Module.require
包裹了一下助泽,它也包含了一些鮮為人知的幫助性的屬性和方法供我們使用:
require()
: 加載一個(gè)外部模塊require.resolve()
: 通過(guò)解析一個(gè)模塊絕對(duì)路徑來(lái)生成模塊的namerequire.main
: 主要模塊require.cache
: 所有模塊的緩存require.extensions
: 每一個(gè)有效文件的編譯函數(shù)都是基于這個(gè)來(lái)做擴(kuò)展
一旦require
完成了汁雷,整個(gè)加載好的源碼會(huì)被包裹在一個(gè)新的函數(shù)里面,同時(shí)傳入require
, module
, exports
和其對(duì)外的變量作為新函數(shù)的參數(shù)报咳。這樣就創(chuàng)造了一個(gè)新的函數(shù)作用域,這樣可以避免污染Node的全局環(huán)境挖藏。
(function (exports, require, module, __filename, __dirname) {
// 你的代碼會(huì)被放在這里
});
最后暑刃,這個(gè)包含了模塊的函數(shù)會(huì)被運(yùn)行。整個(gè)Module._compile
方法的執(zhí)行時(shí)同步的膜眠,所以Module._load
會(huì)等待Module._compile
執(zhí)行完岩臣,然后會(huì)返回module.exports
給用戶。
結(jié)論
至此宵膨,我們已經(jīng)看完了require的代碼架谎,通過(guò)這一圈的代碼就創(chuàng)造出了我們一開(kāi)始想要去了解的那個(gè)require
函數(shù)。
如果你了解了上面所有的內(nèi)容辟躏,那么你將會(huì)了解到require('module')
最后的秘密谷扣。沒(méi)錯(cuò),就是模塊系統(tǒng)本身也可以通過(guò)模塊系統(tǒng)加載進(jìn)來(lái)的。一開(kāi)始会涎,這個(gè)可能聽(tīng)上去有些奇怪裹匙,但是這樣可以讓用戶不用了解Node.js的核心原理就可以使用加載系統(tǒng)加載自己的模塊。流行的模塊例如mockery and rewire就是這樣構(gòu)建的末秃。
如果你想了解更多的細(xì)節(jié)概页,你可以瀏覽module.js源碼。你會(huì)得到更多的信息也會(huì)了解的更多练慕,我將會(huì)給第一個(gè)回答出什么是‘NODE_MODULE_CONTEXTS’并且為什么要加上這個(gè)的人加分惰匙。
[1] module._compile
函數(shù)只是用于運(yùn)行JavaScript文件。JSON文件會(huì)簡(jiǎn)單地通過(guò) JSON.parse()
解析然后返回铃将。
[2] 當(dāng)然這些模塊都是由一些私有的函數(shù)方法構(gòu)建的项鬼,例如Module._resolveLookupPaths
and Module._findPath
。你可以想一想是否可以有更好的辦法...
請(qǐng)開(kāi)啟你的Javascript然后瀏覽 comments powered by Disqus.