新聞中心
在node.js中,模塊使用CommonJS規(guī)范,一個(gè)文件是一個(gè)模塊
員工經(jīng)過長(zhǎng)期磨合與沉淀,具備了協(xié)作精神,得以通過團(tuán)隊(duì)的力量開發(fā)出優(yōu)質(zhì)的產(chǎn)品。創(chuàng)新互聯(lián)建站堅(jiān)持“專注、創(chuàng)新、易用”的產(chǎn)品理念,因?yàn)椤皩W⑺詫I(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡(jiǎn)單”。公司專注于為企業(yè)提供成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、電商網(wǎng)站開發(fā),小程序定制開發(fā),軟件按需網(wǎng)站開發(fā)等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。
node.js中的模塊可分為三類
- 內(nèi)部模塊 - node.js提供的模塊如 fs,http,path等
- 自定模塊 - 我們自己寫的模塊
- 第三方模塊 - 通過npm安裝的模塊
node.js提供了大量的模塊供我們使用,比如 想解析一個(gè)文件的路徑,可以使用path模塊下的相應(yīng)方法實(shí)現(xiàn):
const path = require('path'); //返回目標(biāo)文件的絕對(duì)路徑 console.log(path.resolve('./1.txt'));
運(yùn)行結(jié)果:
/Users/cuiyue/workspace/test/1.txt
使用require引入相應(yīng)的模塊,即可使用。
__dirname和__filename
node.js的每個(gè)模塊都有這兩個(gè)參數(shù),它們都是一個(gè)絕對(duì)路徑的地址,區(qū)別是__filename存放了從根目錄到當(dāng)前文件名的路徑,__dirname只存放從根目錄到模塊的所在目錄:
console.log(__dirname); console.log(__filename);
運(yùn)行結(jié)果:
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/module.js
vm模塊
vm模塊是node.js提供在V8虛擬機(jī)中編譯和運(yùn)行的工具,node.js中的模塊內(nèi)部實(shí)現(xiàn)就是通過此模塊完成。
說說vm的基本用法。
在js環(huán)境中有一個(gè)eval函數(shù),它可以運(yùn)行js的代碼字符串,比如:
eval('console.log("Hello javascript.")'); //輸出Hello javascript.
可以看到,eval函數(shù)的參數(shù)是一段字符串,它可以運(yùn)行字符串形式的js代碼,但它可以使用上下文環(huán)境中的變量:
var num=100; eval('console.log(num)'); //輸出100
以上是可以正確訪問num的值。
vm模塊提供了方法創(chuàng)建一個(gè)安全的沙箱,在指定的上下文環(huán)境中運(yùn)行代碼,不受外界干擾。
const vm = require('vm'); var num = 100; vm.runInThisContext('console.log(num)');
運(yùn)行結(jié)果:
console.log(num)
^
ReferenceError: num is not defined
可以看到代碼報(bào)錯(cuò)了,說明在vm創(chuàng)建了指定的上下文環(huán)境中,拿不到外界的參量。
CommonJS規(guī)范
在以前,由于javascript的歷史原因?qū)е滤哪K機(jī)制很差,由于這些缺點(diǎn)使得javascript不太善于開發(fā)大型應(yīng)用,于是提出了CommonJS規(guī)范以彌補(bǔ)javascript的不足。
CommonJS規(guī)范主要分為三塊內(nèi)容:模塊導(dǎo)入導(dǎo)出、模塊定義、模塊標(biāo)識(shí)。
模塊導(dǎo)入導(dǎo)出
CommonJS中使用require()函數(shù)進(jìn)行模塊的引入。
const mymodule = require('mymodule');
使用exports導(dǎo)出模塊
module.exports = { name: 'Tom' };
引用的名稱可以不帶路徑,若不帶路徑表示引入的是node提供的模塊或是npm安裝的第三方模塊(node_modules)
模塊定義
module對(duì)象:在每一個(gè)模塊中,module對(duì)象代表該模塊自身。
export屬性:module對(duì)象的一個(gè)屬性,它向外提供接口。
模塊標(biāo)識(shí)
模塊標(biāo)識(shí)指的是傳遞給require方法的參數(shù),必須是符合小駝峰命名的字符串,或者以 .、..、開頭的相對(duì)路徑,或者絕對(duì)路徑。
node中模塊解析流程
- 首先接收參數(shù),把傳入的模塊名稱解析成絕對(duì)路徑
- 若沒有后綴名稱,依次拼接.js .json .node嘗試加載,仍到不到模塊則報(bào)錯(cuò)
- 取得正確的路徑后判斷緩存中是否存在此模塊,若有則取出
- 若緩存中不存在則加載此文件,在外包裹一層閉包并執(zhí)行它
以上為大致流程,下面嘗試著寫一下模塊。
代碼的基本結(jié)構(gòu):
/** * Module類,用于處理模塊加載 */ function Module() {} //模塊的緩存 Module._cacheModule = {}; //不同擴(kuò)展名的加載策略 Module._extensions = {}; //根據(jù)moduleId解析絕對(duì)路徑, Module._resolveFileName = function(moduleId) {}; //入口函數(shù) function req(moduleId) {}
附上全部代碼:
const path = require('path'); const fs = require('fs'); const vm = require('vm'); /** * Module類,用于處理模塊加載 */ function Module(file) { this.id = file; //當(dāng)前模塊的id,它使用完整的絕對(duì)路徑標(biāo)識(shí),因此是唯一的 this.exports = {}; //導(dǎo)出 this.loaded = false; //模塊是否已加載完畢 } //模塊的緩存 Module._cacheModule = {}; Module._wrapper = ['(function(exports,require,module,__dirname,__filename){', '});']; //不同擴(kuò)展名的加載策略 Module._extensions = { '.js': function(currentModule) { let js = fs.readFileSync(currentModule.id, 'utf8'); //讀取出js文件內(nèi)容 let fn = Module._wrapper[0] + js + Module._wrapper[1]; vm.runInThisContext(fn).call( currentModule.exports, currentModule.exports, req, currentModule, path.dirname(currentModule.id), currentModule.id); return currentModule.exports; }, '.json': function(currentModule) { let json = fs.readFileSync(currentModule.id, 'utf8'); return JSON.parse(json); //轉(zhuǎn)換為JSON對(duì)象返回 }, '.node': '' }; //加載模塊(實(shí)例方法) Module.prototype.load = function(file) { let extname = path.extname(file); //獲取后綴名 return Module._extensions[extname](this); }; //根據(jù)moduleId解析絕對(duì)路徑, Module._resolveFileName = function(moduleId) { let p = path.resolve(moduleId); if (!path.extname(moduleId)) { //傳入的模塊沒有后綴 let arr = Object.keys(Module._extensions); //循環(huán)讀取不同擴(kuò)展名的文件 for (var i = 0; i < arr.length; i++) { let file = p + arr[i]; //拼接上后綴名成為一個(gè)完整的路徑 try { fs.accessSync(file); return file; //若此文件存在返回它 } catch (e) { console.log(e); } } } else { return p; } }; function req(moduleId) { let file = Module._resolveFileName(moduleId); if (Module._cacheModule[file]) { //若緩存中存在此模塊 return Module._cacheModule[file]; } else { let module = new Module(file); module.exports = module.load(file); return module.exports; } } console.log(req('./a.js')());
a.js的文件內(nèi)容:
module.exports = function() { console.log('This message from a.js'); console.log(__dirname); console.log(__filename); }
最終運(yùn)行結(jié)果:
This message from a.js
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/a.js
重要代碼說明
_resolveFileName
_resolveFileName方法的主要作用是把傳入的模塊解析成絕對(duì)路徑,這樣才可以進(jìn)行下一步,根據(jù)完整的路徑加載模塊。
因此要進(jìn)行判斷,如果傳入的模塊不存在,則要報(bào)錯(cuò);如果傳入的模塊已經(jīng)有擴(kuò)展名了,就不要拼接了;若沒有擴(kuò)展名,依次以.js .json .node的順序拼接成完成的模塊進(jìn)行加載。
_extensions
此對(duì)象中封裝了加載不同類型模塊的處理方法,其中若是.json類型則使用fs讀取文件直接轉(zhuǎn)換成JSON對(duì)象并返回。
若是.js文件則讀取后,拼接閉包,將exports,require,module,__dirname,__filename五大參數(shù)拼接好,使用vm模塊的沙箱機(jī)制運(yùn)行,得到的結(jié)果放入module.exports返回。
總結(jié)
以上就是node.js的模塊加載的簡(jiǎn)單邏輯,實(shí)際上node.js的源碼遠(yuǎn)遠(yuǎn)比上面的代碼復(fù)雜,光是處理模塊路徑、判斷合法等操作就寫了N行。而且我這里沒有寫緩存以及其它的復(fù)雜邏輯,但核心差不多就是這些,核心的核心就是用fs.readFileSync讀取js文件,把內(nèi)容拼接到一個(gè)大大的閉包中,這也解釋了為什么我們自己寫的所有node模塊中都會(huì)有require方法,exports導(dǎo)出,以及__dirname和__filename參數(shù)。
了解了node.js的模塊加載邏輯,在以后寫node.js就更可避免一些誤解,寫出精細(xì)的代碼。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
文章題目:淺析node.js的模塊加載機(jī)制
網(wǎng)頁URL:http://www.ef60e0e.cn/article/pjpcjp.html