真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Node.js中模塊加載機(jī)制的原理是什么

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)Node.js中模塊加載機(jī)制的原理是什么,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

十載的名山網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都營(yíng)銷網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整名山建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“名山網(wǎng)站設(shè)計(jì)”,“名山網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

簡(jiǎn)單例子

老規(guī)矩,講原理前我們先來一個(gè)簡(jiǎn)單的例子,從這個(gè)例子入手一步一步深入原理。Node.js里面如果要導(dǎo)出某個(gè)內(nèi)容,需要使用module.exports,使用module.exports幾乎可以導(dǎo)出任意類型的JS對(duì)象,包括字符串,函數(shù),對(duì)象,數(shù)組等等。我們先來建一個(gè)a.js導(dǎo)出一個(gè)最簡(jiǎn)單的hello world:

// a.js   module.exports = "hello world";

然后再來一個(gè)b.js導(dǎo)出一個(gè)函數(shù):

// b.js  function add(a, b) {    return a + b;  }  module.exports = add;

然后在index.js里面使用他們,即require他們,require函數(shù)返回的結(jié)果就是對(duì)應(yīng)文件module.exports的值:

// index.js  const a = require('./a.js');  const add = require('./b.js'); console.log(a);      // "hello world"  console.log(add(1, 2));    // b導(dǎo)出的是一個(gè)加法函數(shù),可以直接使用,這行結(jié)果是3

require會(huì)先運(yùn)行目標(biāo)文件

當(dāng)我們r(jià)equire某個(gè)模塊時(shí),并不是只拿他的module.exports,而是會(huì)從頭開始運(yùn)行這個(gè)文件,module.exports = XXX其實(shí)也只是其中一行代碼,我們后面會(huì)講到,這行代碼的效果其實(shí)就是修改模塊里面的exports屬性。比如我們?cè)賮硪粋€(gè)c.js:

// c.js  let c = 1; cc = c + 1;  module.exports = c;  c = 6;

在c.js里面我們導(dǎo)出了一個(gè)c,這個(gè)c經(jīng)過了幾步計(jì)算,當(dāng)運(yùn)行到module.exports = c;這行時(shí)c的值為2,所以我們r(jià)equire的c.js的值就是2,后面將c的值改為了6并不影響前面的這行代碼:

const c = require('./c.js');  console.log(c);  // c的值是2

前面c.js的變量c是一個(gè)基本數(shù)據(jù)類型,所以后面的c = 6;不影響前面的module.exports,那他如果是一個(gè)引用類型呢?我們直接來試試吧:

// d.js  let d = {    num: 1  };  d.num++;  module.exports = d; d.num = 6;

然后在index.js里面require他:

const d = require('./d.js');  console.log(d);     // { num: 6 }

我們發(fā)現(xiàn)在module.exports后面給d.num賦值仍然生效了,因?yàn)閐是一個(gè)對(duì)象,是一個(gè)引用類型,我們可以通過這個(gè)引用來修改他的值。其實(shí)對(duì)于引用類型來說,不僅僅在module.exports后面可以修改他的值,在模塊外面也可以修改,比如index.js里面就可以直接改:

const d = require('./d.js');  d.num = 7;  console.log(d);     // { num: 7 }

require和module.exports不是黑魔法

我們通過前面的例子可以看出來,require和module.exports干的事情并不復(fù)雜,我們先假設(shè)有一個(gè)全局對(duì)象{},初始情況下是空的,當(dāng)你require某個(gè)文件時(shí),就將這個(gè)文件拿出來執(zhí)行,如果這個(gè)文件里面存在module.exports,當(dāng)運(yùn)行到這行代碼時(shí)將module.exports的值加入這個(gè)對(duì)象,鍵為對(duì)應(yīng)的文件名,最終這個(gè)對(duì)象就長(zhǎng)這樣:

{    "a.js": "hello world",    "b.js": function add(){},    "c.js": 2,    "d.js": { num: 2 }  }

當(dāng)你再次require某個(gè)文件時(shí),如果這個(gè)對(duì)象里面有對(duì)應(yīng)的值,就直接返回給你,如果沒有就重復(fù)前面的步驟,執(zhí)行目標(biāo)文件,然后將它的module.exports加入這個(gè)全局對(duì)象,并返回給調(diào)用者。這個(gè)全局對(duì)象其實(shí)就是我們經(jīng)常聽說的緩存。所以require和module.exports并沒有什么黑魔法,就只是運(yùn)行并獲取目標(biāo)文件的值,然后加入緩存,用的時(shí)候拿出來用就行。再看看這個(gè)對(duì)象,因?yàn)閐.js是一個(gè)引用類型,所以你在任何地方獲取了這個(gè)引用都可以更改他的值,如果不希望自己模塊的值被更改,需要自己寫模塊時(shí)進(jìn)行處理,比如使用Object.freeze(),Object.defineProperty()之類的方法。

模塊類型和加載順序

這一節(jié)的內(nèi)容都是一些概念,比較枯燥,但是也是我們需要了解的。

模塊類型

Node.js的模塊有好幾種類型,前面我們使用的其實(shí)都是文件模塊,總結(jié)下來,主要有這兩種類型:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   內(nèi)置模塊:就是Node.js原生提供的功能,比如fs,http等等,這些模塊在Node.js進(jìn)程起來時(shí)就加載了。

  3.   文件模塊:我們前面寫的幾個(gè)模塊,還有第三方模塊,即node_modules下面的模塊都是文件模塊。

加載順序

加載順序是指當(dāng)我們r(jià)equire(X)時(shí),應(yīng)該按照什么順序去哪里找X,在官方文檔上有詳細(xì)偽代碼,總結(jié)下來大概是這么個(gè)順序:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   優(yōu)先加載內(nèi)置模塊,即使有同名文件,也會(huì)優(yōu)先使用內(nèi)置模塊。

  3.   不是內(nèi)置模塊,先去緩存找。

  4.   緩存沒有就去找對(duì)應(yīng)路徑的文件。

  5.   不存在對(duì)應(yīng)的文件,就將這個(gè)路徑作為文件夾加載。

  6.   對(duì)應(yīng)的文件和文件夾都找不到就去node_modules下面找。

  7.   還找不到就報(bào)錯(cuò)了。

加載文件夾

前面提到找不到文件就找文件夾,但是不可能將整個(gè)文件夾都加載進(jìn)來,加載文件夾的時(shí)候也是有一個(gè)加載順序的:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   先看看這個(gè)文件夾下面有沒有package.json,如果有就找里面的main字段,main字段有值就加載對(duì)應(yīng)的文件。所以如果大家在看一些第三方庫源碼時(shí)找不到入口就看看他package.json里面的main字段吧,比如jquery的main字段就是這樣:"main": "dist/jquery.js"。

     2.  如果沒有package.json或者package.json里面沒有main就找index文件。

     3.   如果這兩步都找不到就報(bào)錯(cuò)了。

支持的文件類型

require主要支持三種文件類型:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   .js:.js文件是我們最常用的文件類型,加載的時(shí)候會(huì)先運(yùn)行整個(gè)JS文件,然后將前面說的module.exports作為require的返回值。

  3.   .json:.json文件是一個(gè)普通的文本文件,直接用JSON.parse將其轉(zhuǎn)化為對(duì)象返回就行。

  4.   .node:.node文件是C++編譯后的二進(jìn)制文件,純前端一般很少接觸這個(gè)類型。

手寫require

前面其實(shí)我們已經(jīng)將原理講的七七八八了,下面來到我們的重頭戲,自己實(shí)現(xiàn)一個(gè)require。實(shí)現(xiàn)require其實(shí)就是實(shí)現(xiàn)整個(gè)Node.js的模塊加載機(jī)制,我們?cè)賮砝硪幌滦枰鉀Q的問題:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   通過傳入的路徑名找到對(duì)應(yīng)的文件。

  3.   執(zhí)行找到的文件,同時(shí)要注入module和require這些方法和屬性,以便模塊文件使用。

  4.   返回模塊的module.exports

本文的手寫代碼全部參照Node.js官方源碼,函數(shù)名和變量名盡量保持一致,其實(shí)就是精簡(jiǎn)版的源碼,大家可以對(duì)照著看,寫到具體方法時(shí)我也會(huì)貼上對(duì)應(yīng)的源碼地址??傮w的代碼都在這個(gè)文件里面:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js

Module類

Node.js模塊加載的功能全部在Module類里面,整個(gè)代碼使用面向?qū)ο蟮乃枷?,如果你?duì)JS的面向?qū)ο筮€不是很熟悉可以先看看這篇文章。Module類的構(gòu)造函數(shù)也不復(fù)雜,主要是一些值的初始化,為了跟官方Module名字區(qū)分開,我們自己的類命名為MyModule:

function MyModule(id = '') {    this.id = id;       // 這個(gè)id其實(shí)就是我們r(jià)equire的路徑    this.path = path.dirname(id);     // path是Node.js內(nèi)置模塊,用它來獲取傳入?yún)?shù)對(duì)應(yīng)的文件夾路徑    this.exports = {};        // 導(dǎo)出的東西放這里,初始化為空對(duì)象    this.filename = null;     // 模塊對(duì)應(yīng)的文件名    this.loaded = false;      // loaded用來標(biāo)識(shí)當(dāng)前模塊是否已經(jīng)加載  }

require方法

我們一直用的require其實(shí)是Module類的一個(gè)實(shí)例方法,內(nèi)容很簡(jiǎn)單,先做一些參數(shù)檢查,然后調(diào)用Module._load方法,源碼看這里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L970。精簡(jiǎn)版的代碼如下:

MyModule.prototype.require = function (id) {    return Module._load(id);  }

MyModule._load

MyModule._load是一個(gè)靜態(tài)方法,這才是require方法的真正主體,他干的事情其實(shí)是:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   先檢查請(qǐng)求的模塊在緩存中是否已經(jīng)存在了,如果存在了直接返回緩存模塊的exports。

  3.   如果不在緩存中,就new一個(gè)Module實(shí)例,用這個(gè)實(shí)例加載對(duì)應(yīng)的模塊,并返回模塊的exports。

我們自己來實(shí)現(xiàn)下這兩個(gè)需求,緩存直接放在Module._cache這個(gè)靜態(tài)變量上,這個(gè)變量官方初始化使用的是Object.create(null),這樣可以使創(chuàng)建出來的原型指向null,我們也這樣做吧:

MyModule._cache = Object.create(null);  MyModule._load = function (request) {    // request是我們傳入的路勁參數(shù)    const filename = MyModule._resolveFilename(request);    // 先檢查緩存,如果緩存存在且已經(jīng)加載,直接返回緩存    const cachedModule = MyModule._cache[filename];    if (cachedModule !== undefined) {      return cachedModule.exports;   }    // 如果緩存不存在,我們就加載這個(gè)模塊    // 加載前先new一個(gè)MyModule實(shí)例,然后調(diào)用實(shí)例方法load來加載    // 加載完成直接返回module.exports    const module = new MyModule(filename);    // load之前就將這個(gè)模塊緩存下來,這樣如果有循環(huán)引用就會(huì)拿到這個(gè)緩存,但是這個(gè)緩存里面的exports可能還沒有或者不完整    MyModule._cache[filename] = module;    module.load(filename);    return module.exports;  }

上述代碼對(duì)應(yīng)的源碼看這里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L735

可以看到上述源碼還調(diào)用了兩個(gè)方法:MyModule._resolveFilename和MyModule.prototype.load,下面我們來實(shí)現(xiàn)下這兩個(gè)方法。

MyModule._resolveFilename

MyModule._resolveFilename從名字就可以看出來,這個(gè)方法是通過用戶傳入的require參數(shù)來解析到真正的文件地址的,源碼中這個(gè)方法比較復(fù)雜,因?yàn)榘凑涨懊嬷v的,他要支持多種參數(shù):內(nèi)置模塊,相對(duì)路徑,絕對(duì)路徑,文件夾和第三方模塊等等,如果是文件夾或者第三方模塊還要解析里面的package.json和index.js。我們這里主要講原理,所以我們就只實(shí)現(xiàn)通過相對(duì)路徑和絕對(duì)路徑來查找文件,并支持自動(dòng)添加js和json兩種后綴名:

MyModule._resolveFilename = function (request) {    const filename = path.resolve(request);   // 獲取傳入?yún)?shù)對(duì)應(yīng)的絕對(duì)路徑    const extname = path.extname(request);    // 獲取文件后綴名   // 如果沒有文件后綴名,嘗試添加.js和.json    if (!extname) {      const exts = Object.keys(MyModule._extensions);      for (let i = 0; i < exts.length; i++) {        const currentPath = `${filename}${exts[i]}`;        // 如果拼接后的文件存在,返回拼接的路徑        if (fs.existsSync(currentPath)) {          return currentPath;        }      }    }    return filename;  }

上述源碼中我們還用到了一個(gè)靜態(tài)變量MyModule._extensions,這個(gè)變量是用來存各種文件對(duì)應(yīng)的處理方法的,我們后面會(huì)實(shí)現(xiàn)他。

MyModule._resolveFilename對(duì)應(yīng)的源碼看這里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L822

MyModule.prototype.load

MyModule.prototype.load是一個(gè)實(shí)例方法,這個(gè)方法就是真正用來加載模塊的方法,這其實(shí)也是不同類型文件加載的一個(gè)入口,不同類型的文件會(huì)對(duì)應(yīng)MyModule._extensions里面的一個(gè)方法:

MyModule.prototype.load = function (filename) {    // 獲取文件后綴名    const extname = path.extname(filename);    // 調(diào)用后綴名對(duì)應(yīng)的處理函數(shù)來處理    MyModule._extensions[extname](this, filename);    this.loaded = true;  }

注意這段代碼里面的this指向的是module實(shí)例,因?yàn)樗且粋€(gè)實(shí)例方法。對(duì)應(yīng)的源碼看這里: https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L942

加載js文件: MyModule._extensions['.js']

前面我們說過不同文件類型的處理方法都掛載在MyModule._extensions上面的,我們先來實(shí)現(xiàn).js類型文件的加載:

MyModule._extensions['.js'] = function (module, filename) {    const content = fs.readFileSync(filename, 'utf8');    module._compile(content, filename);  }

可以看到j(luò)s的加載方法很簡(jiǎn)單,只是把文件內(nèi)容讀出來,然后調(diào)了另外一個(gè)實(shí)例方法_compile來執(zhí)行他。對(duì)應(yīng)的源碼看這里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L1098

編譯執(zhí)行js文件:MyModule.prototype._compile

MyModule.prototype._compile是加載JS文件的核心所在,也是我們最常使用的方法,這個(gè)方法需要將目標(biāo)文件拿出來執(zhí)行一遍,執(zhí)行之前需要將它整個(gè)代碼包裹一層,以便注入exports, require, module, __dirname, __filename,這也是我們能在JS文件里面直接使用這幾個(gè)變量的原因。要實(shí)現(xiàn)這種注入也不難,假如我們r(jià)equire的文件是一個(gè)簡(jiǎn)單的Hello World,長(zhǎng)這樣:

module.exports = "hello world";

那我們?cè)趺磥斫o他注入module這個(gè)變量呢?答案是執(zhí)行的時(shí)候在他外面再加一層函數(shù),使他變成這樣:

function (module) { // 注入module變量,其實(shí)幾個(gè)變量同理    module.exports = "hello world";  }

所以我們?nèi)绻麑⑽募?nèi)容作為一個(gè)字符串的話,為了讓他能夠變成上面這樣,我們需要再給他拼接上開頭和結(jié)尾,我們直接將開頭和結(jié)尾放在一個(gè)數(shù)組里面:

MyModule.wrapper = [    '(function (exports, require, module, __filename, __dirname) { ',    '\n});'  ];

注意我們拼接的開頭和結(jié)尾多了一個(gè)()包裹,這樣我們后面可以拿到這個(gè)匿名函數(shù),在后面再加一個(gè)()就可以傳參數(shù)執(zhí)行了。然后將需要執(zhí)行的函數(shù)拼接到這個(gè)方法中間:

MyModule.wrap = function (script) {    return MyModule.wrapper[0] + script + MyModule.wrapper[1];  };

這樣通過MyModule.wrap包裝的代碼就可以獲取到exports, require, module, __filename, __dirname這幾個(gè)變量了。知道了這些就可以來寫MyModule.prototype._compile了:

MyModule.prototype._compile = function (content, filename) {    const wrapper = Module.wrap(content);    // 獲取包裝后函數(shù)體    // vm是nodejs的虛擬機(jī)沙盒模塊,runInThisContext方法可以接受一個(gè)字符串并將它轉(zhuǎn)化為一個(gè)函數(shù)    // 返回值就是轉(zhuǎn)化后的函數(shù),所以compiledWrapper是一個(gè)函數(shù)    const compiledWrapper = vm.runInThisContext(wrapper, {      filename,      lineOffset: 0,      displayErrors: true,    });    // 準(zhǔn)備exports, require, module, __filename, __dirname這幾個(gè)參數(shù)    // exports可以直接用module.exports,即this.exports    // require官方源碼中還包裝了一層,其實(shí)最后調(diào)用的還是this.require    // module不用說,就是this了    // __filename直接用傳進(jìn)來的filename參數(shù)了    // __dirname需要通過filename獲取下    const dirname = path.dirname(filename);    compiledWrapper.call(this.exports, this.exports, this.require, this,      filename, dirname);  }

上述代碼要注意我們注入進(jìn)去的幾個(gè)參數(shù)和通過call傳進(jìn)去的this:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   this:compiledWrapper是通過call調(diào)用的,第一個(gè)參數(shù)就是里面的this,這里我們傳入的是this.exports,也就是module.exports,也就是說我們js文件里面this是對(duì)module.exports的一個(gè)引用。

  3.   exports: compiledWrapper正式接收的第一個(gè)參數(shù)是exports,我們傳的也是this.exports,所以js文件里面的exports也是對(duì)module.exports的一個(gè)引用。

  4.   require: 這個(gè)方法我們傳的是this.require,其實(shí)就是MyModule.prototype.require,也就是MyModule._load。

  5.   module: 我們傳入的是this,也就是當(dāng)前模塊的實(shí)例。

  6.   __filename:文件所在的絕對(duì)路徑。

  7.   __dirname: 文件所在文件夾的絕對(duì)路徑。

到這里,我們的JS文件其實(shí)已經(jīng)記載完了,對(duì)應(yīng)的源碼看這里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L1043

加載json文件: MyModule._extensions['.json']

加載json文件就簡(jiǎn)單多了,只需要將文件讀出來解析成json就行了:

MyModule._extensions['.json'] = function (module, filename) {    const content = fs.readFileSync(filename, 'utf8');    module.exports = JSONParse(content);  }

exports和module.exports的區(qū)別

網(wǎng)上經(jīng)常有人問,node.js里面的exports和module.exports到底有什么區(qū)別,其實(shí)前面我們的手寫代碼已經(jīng)給出答案了,我們這里再就這個(gè)問題詳細(xì)講解下。exports和module.exports這兩個(gè)變量都是通過下面這行代碼注入的。

compiledWrapper.call(this.exports, this.exports, this.require, this,      filename, dirname);

初始狀態(tài)下,exports === module.exports === {},exports是module.exports的一個(gè)引用,如果你一直是這樣使用的:

exports.a = 1;  module.exports.b = 2; console.log(exports === module.exports);   // true

上述代碼中,exports和module.exports都是指向同一個(gè)對(duì)象{},你往這個(gè)對(duì)象上添加屬性并沒有改變這個(gè)對(duì)象本身的引用地址,所以exports === module.exports一直成立。

但是如果你哪天這樣使用了:

exports = {    a: 1  }

或者這樣使用了:

module.exports = {      b: 2  }

那其實(shí)你是給exports或者module.exports重新賦值了,改變了他們的引用地址,那這兩個(gè)屬性的連接就斷開了,他們就不再相等了。需要注意的是,你對(duì)module.exports的重新賦值會(huì)作為模塊的導(dǎo)出內(nèi)容,但是你對(duì)exports的重新賦值并不能改變模塊導(dǎo)出內(nèi)容,只是改變了exports這個(gè)變量而已,因?yàn)槟K始終是module,導(dǎo)出內(nèi)容是module.exports。

循環(huán)引用

Node.js對(duì)于循環(huán)引用是進(jìn)行了處理的,下面是官方例子:

a.js:

console.log('a 開始');  exports.done = false;  const b = require('./b.js');  console.log('在 a 中,b.done = %j', b.done);  exports.done = true;  console.log('a 結(jié)束');

b.js:

console.log('b 開始');  exports.done = false;  const a = require('./a.js');  console.log('在 b 中,a.done = %j', a.done);  exports.done = true;  console.log('b 結(jié)束');

main.js:

console.log('main 開始');  const a = require('./a.js');  const b = require('./b.js');  console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);

當(dāng) main.js 加載 a.js 時(shí), a.js 又加載 b.js。 此時(shí), b.js 會(huì)嘗試去加載 a.js。 為了防止無限的循環(huán),會(huì)返回一個(gè) a.js 的 exports 對(duì)象的 未完成的副本 給 b.js 模塊。 然后 b.js 完成加載,并將 exports 對(duì)象提供給 a.js 模塊。

那么這個(gè)效果是怎么實(shí)現(xiàn)的呢?答案就在我們的MyModule._load源碼里面,注意這兩行代碼的順序:

MyModule._cache[filename] = module;  module.load(filename);

上述代碼中我們是先將緩存設(shè)置了,然后再執(zhí)行的真正的load,順著這個(gè)思路我能來理一下這里的加載流程:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.   main加載a,a在真正加載前先去緩存中占一個(gè)位置

  3.   a在正式加載時(shí)加載了b

  4.   b又去加載了a,這時(shí)候緩存中已經(jīng)有a了,所以直接返回a.exports,即使這時(shí)候的exports是不完整的。

總結(jié)

  1.  require不是黑魔法,整個(gè)Node.js的模塊加載機(jī)制都是JS實(shí)現(xiàn)的。

  2.  每個(gè)模塊里面的exports, require, module, __filename, __dirname五個(gè)參數(shù)都不是全局變量,而是模塊加載的時(shí)候注入的。

  3.  為了注入這幾個(gè)變量,我們需要將用戶的代碼用一個(gè)函數(shù)包裹起來,拼一個(gè)字符串然后調(diào)用沙盒模塊vm來實(shí)現(xiàn)。

  4.  初始狀態(tài)下,模塊里面的this, exports, module.exports都指向同一個(gè)對(duì)象,如果你對(duì)他們重新賦值,這種連接就斷了。

  5.  對(duì)module.exports的重新賦值會(huì)作為模塊的導(dǎo)出內(nèi)容,但是你對(duì)exports的重新賦值并不能改變模塊導(dǎo)出內(nèi)容,只是改變了exports這個(gè)變量而已,因?yàn)槟K始終是module,導(dǎo)出內(nèi)容是module.exports。

    6.  為了解決循環(huán)引用,模塊在加載前就會(huì)被加入緩存,下次再加載會(huì)直接返回緩存,如果這時(shí)候模塊還沒加載完,你可能拿到未完成的exports。

    7.  Node.js實(shí)現(xiàn)的這套加載機(jī)制叫CommonJS。

上述就是小編為大家分享的Node.js中模塊加載機(jī)制的原理是什么了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


網(wǎng)站標(biāo)題:Node.js中模塊加載機(jī)制的原理是什么
文章網(wǎng)址:http://weahome.cn/article/jjphcd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部