国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

Node.js模塊化機(jī)制原理探究

aikin / 1874人閱讀

摘要:要想讓模塊再次運(yùn)行,必須清除緩存。用戶(hù)自己編寫(xiě)的模塊,稱(chēng)為文件模塊。并且和指向了同一個(gè)模塊對(duì)象。模塊路徑這是在定位文件模塊的具體文件時(shí)指定的查找策略,具體表現(xiàn)為一個(gè)路徑組成的數(shù)組。

前言

Node應(yīng)用是由模塊組成的,Node遵循了CommonJS的模塊規(guī)范,來(lái)隔離每個(gè)模塊的作用域,使每個(gè)模塊在它自身的命名空間中執(zhí)行。

CommonJS規(guī)范的主要內(nèi)容:

模塊必須通過(guò) module.exports 導(dǎo)出對(duì)外的變量或接口,通過(guò) require() 來(lái)導(dǎo)入其他模塊的輸出到當(dāng)前模塊作用域中。

CommonJS模塊的特點(diǎn):

(1)所有代碼運(yùn)行在當(dāng)前模塊作用域中,不會(huì)污染全局作用域
(2)模塊同步加載,根據(jù)代碼中出現(xiàn)的順序依次加載
(3)模塊可以多次加載,但是只會(huì)在第一次加載時(shí)運(yùn)行一次,然后運(yùn)行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。要想讓模塊再次運(yùn)行,必須清除緩存。

一個(gè)簡(jiǎn)單的例子:

demo.js

module.exports.name = "Aphasia";
module.exports.getAge = function(age){
    console.log(age)
};
//需要引入demo.js的其他文件
var person = require("./demo.js")
module對(duì)象

根據(jù)CommonJS規(guī)范,每一個(gè)文件就是一個(gè)模塊,在每個(gè)模塊中,都會(huì)有一個(gè)module對(duì)象,這個(gè)對(duì)象就指向當(dāng)前的模塊。module對(duì)象具有以下屬性:

(1)id:當(dāng)前模塊的bi
(2)exports:表示當(dāng)前模塊暴露給外部的值
(3)parent: 是一個(gè)對(duì)象,表示調(diào)用當(dāng)前模塊的模塊
(4)children:是一個(gè)對(duì)象,表示當(dāng)前模塊調(diào)用的模塊
(5)filename:模塊的絕對(duì)路徑
(6)paths:從當(dāng)前文件目錄開(kāi)始查找node_modules目錄;然后依次進(jìn)入父目錄,查找父目錄下的node_modules目錄;依次迭代,直到根目錄下的node_modules目錄
(7)loaded:一個(gè)布爾值,表示當(dāng)前模塊是否已經(jīng)被完全加載

示例:

module.js

module.exports = {
    name: "Aphasia",
    getAge: function(age){
            console.log(age)
    }
}
console.log(module)

執(zhí)行node module.js

1、module.exports

從上面的例子我們也能看到,module對(duì)象具有一個(gè)exports屬性,該屬性就是用來(lái)對(duì)外暴露變量、方法或整個(gè)模塊的。當(dāng)其他的文件require進(jìn)來(lái)該模塊的時(shí)候,實(shí)際上就是讀取了該模塊module對(duì)象的exports屬性。

簡(jiǎn)單的使用示例

module.exports = "Aphasia";
module.exports.name = "Aphasia";
module.exports = function(){
    //dosomething
}
module.exports = {
    name: "Aphasia",
    getAge: function(){
        //dosomething
    }
}
2、exports對(duì)象

一開(kāi)始我很郁悶,既然module.exports就能滿(mǎn)足所有的需求,為什么還有個(gè)exports對(duì)象呢?其實(shí),二者之間有下面的關(guān)系

(1)首先,exports和module.exports都是引用類(lèi)型的變量,而且這兩個(gè)對(duì)象指向同一塊內(nèi)存地址。在node中,二者一開(kāi)始都是指向一個(gè)空對(duì)象的

exports = module.exports = {};

可以在REPL環(huán)境中直接運(yùn)行下面代碼module.exports,結(jié)果會(huì)輸出一個(gè){}

(2)其次,exports對(duì)象是通過(guò)形參的方式傳入的,直接賦值形參會(huì)改變形參的引用,但是并不能改變作用域外的值。這句話(huà)是什么意思呢?我們舉個(gè)例子。

var module = {
    exports: {}
}

var exports = module.exports

function change(exports) {
    //為形參添加屬性,是會(huì)同步到外部的module.exports對(duì)象的
    exports.name = "Aphasia"
    //在這里修改了exports的引用,并不會(huì)影響到module.exports
    exports = {
        age: 24
    }
    console.log(exports) //{ age: 24 }
}

change(exports)
console.log(module.exports) //{exports: {name: "Aphasia"}}

現(xiàn)在明白了吧?其實(shí)我們?cè)谀K中像下面的代碼那樣,直接給exports賦值,會(huì)改變當(dāng)前模塊內(nèi)部的形參exports對(duì)象的引用,也就是說(shuō)當(dāng)前的exports已經(jīng)跟外部的module.exports對(duì)象沒(méi)有任何關(guān)系了,所以這個(gè)改變是不會(huì)影響到module.exports的。因此,下面的這種方式是沒(méi)有任何效果的,所有的屬性和方法都不會(huì)被拋出。

//以下操作都是不起作用的
exports = "Aphasia";
exports = function(){
    console.log("Aphasia")
}

其實(shí)module.exports就是為了解決上述exports直接賦值,會(huì)導(dǎo)致拋出不成功的問(wèn)題而產(chǎn)生的。有了它,我們就可以這樣來(lái)拋出一個(gè)模塊了。

//這些操作都是合法的
exports.name = "Aphasia";
exports.getName = function(){
    console.log("Aphasia")
}
//相當(dāng)于下面的方式
module.exports = {
    name: "Aphasia",
    getName: function(){
        console.log("Aphasia")
    }
}

這樣就不用每次把要拋出的對(duì)象或方法賦值給exports的屬性了 ,直接采用對(duì)象字面量的方式更加方便。

模塊實(shí)例的require方法

我們都知道,當(dāng)使用exports或者module.exports拋出一個(gè)模塊,通過(guò)給require()方法傳入模塊標(biāo)識(shí)符參數(shù),然后node根據(jù)一定的規(guī)則引入該模塊之后,我們就能使用模塊中定義的方法和屬性了。這里要講的就是node的模塊引入規(guī)則。

1、node中引入模塊的機(jī)制

在Node中引入模塊,需要經(jīng)歷3個(gè)步驟

(1)路徑分析
(2)文件定位
(3)編譯執(zhí)行

在Node中,模塊一般分為兩種

(1)Node提供的模塊,例如http、fs等,稱(chēng)為核心模塊。核心模塊在node源代碼編譯的過(guò)程中就編譯進(jìn)了二進(jìn)制執(zhí)行文件,在Node進(jìn)程啟動(dòng)的時(shí)候,部分核心模塊就直接加載進(jìn)內(nèi)存中了,因此這部分模塊是不用經(jīng)歷上述的(2)(3)兩個(gè)步驟的,而且在路徑分析中是優(yōu)先判斷的,因此加載速度最快。
(2)用戶(hù)自己編寫(xiě)的模塊,稱(chēng)為文件模塊。文件模塊是按需加載的,需要經(jīng)歷上述的三個(gè)步驟,速度較慢。

優(yōu)先從緩存中加載

與瀏覽器會(huì)緩存靜態(tài)腳本文件以提高頁(yè)面性能一樣,Node對(duì)引入過(guò)的模塊也會(huì)進(jìn)行緩存。不同的地方是,node緩存的是編譯執(zhí)行之后的對(duì)象而不是靜態(tài)文件。這一點(diǎn)我們可以用下面的方式來(lái)驗(yàn)證。

modA.js

console.log("模塊modA開(kāi)始加載...")
exports = function() {
    console.log("Hi")
}
console.log("模塊modA加載完畢")

init.js

    
var mod1 = require("./modA")
var mod2 = require("./modA")
console.log(mod1 === mod2)

執(zhí)行node init.js,運(yùn)行結(jié)果:

雖然我們兩次引入modA這個(gè)模塊,但是模塊中的代碼其實(shí)只執(zhí)行了一遍。并且mod1和mod2指向了同一個(gè)模塊對(duì)象。

下面是Module._load的源碼:

Module._load = function(request, parent, isMain) {

  //  計(jì)算絕對(duì)路徑
  var filename = Module._resolveFilename(request, parent);

  //  第一步:如果有緩存,取出緩存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;

  // 第二步:是否為內(nèi)置模塊
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }

  // 第三步:生成模塊實(shí)例,存入緩存
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  // 第四步:加載模塊
  try {
    module.load(filename);
    hadException = false;
  } finally {
    if (hadException) {
      delete Module._cache[filename];
    }
  }

  // 第五步:輸出模塊的exports屬性
  return module.exports;
};

對(duì)應(yīng)流程如下圖所示:

2、路徑分析和文件定位

路徑分析

模塊標(biāo)識(shí)符分析:
(1)核心模塊,如http、fs、path
(2)以...開(kāi)始的相對(duì)路徑文件模塊
(3)以/開(kāi)始的絕對(duì)路徑文件模塊
(4)非路徑形式的文件模塊

1)核心模塊:優(yōu)先級(jí)僅次于緩存,加載速度最快;如果自定義模塊與核心模塊名稱(chēng)相同,加載是不會(huì)成功的。若想加載成功,必須選擇一個(gè)不同的名稱(chēng)或者換用路徑。

2)路徑形式的文件模塊:以. || .. || /開(kāi)始的標(biāo)識(shí)符,都會(huì)被當(dāng)做文件模塊來(lái)處理。在加載的過(guò)程中,require方法會(huì)將路徑轉(zhuǎn)換為真實(shí)的路徑,加載速度僅次于核心模塊

3) 非路徑形式的自定義模塊:這是一種特殊的文件模塊,可能是一個(gè)文件或者包的形式。查找這類(lèi)模塊的策略類(lèi)似于JS中作用域鏈,Node會(huì)逐個(gè)嘗試模塊路徑中的路徑,直到找到目標(biāo)文件為止。

模塊路徑: 這是Node在定位文件模塊的具體文件時(shí)指定的查找策略,具體表現(xiàn)為一個(gè)路徑組成的數(shù)組。

可以在REPL環(huán)境中輸出Module對(duì)象,查看其path屬性的方式查看上述數(shù)組

文件定位

文件擴(kuò)展名分析

require()分析的標(biāo)識(shí)符可以不包含擴(kuò)展名,node會(huì)按.js、.node、.json的次序補(bǔ)足擴(kuò)展名,依次嘗試

目標(biāo)分析和包

如果在擴(kuò)展名分析的步驟中,查找不到文件而是查找到相應(yīng)目錄,此時(shí)node會(huì)將目錄當(dāng)做包來(lái)處理,進(jìn)行下一步分析查找當(dāng)前目錄下package.json中的main屬性指定的文件名,若查找不成功則依次查找index.js,index.node,index.json。

如果目錄分析的過(guò)程中沒(méi)有定位到任何文件,則自定義模塊會(huì)進(jìn)入下一個(gè)模塊路徑繼續(xù)查找,直到所有的模塊路徑都遍歷完畢,依然沒(méi)找到則拋出查找失敗的異常。

參考源碼

在Module._load方法的內(nèi)部調(diào)用了Module._findPath這個(gè)方法,這個(gè)方法是用來(lái)返回模塊的絕對(duì)路徑的,源碼如下:

Module._findPath = function(request, paths) {

  // 列出所有可能的后綴名:.js,.json, .node
  var exts = Object.keys(Module._extensions);

  // 如果是絕對(duì)路徑,就不再搜索
  if (request.charAt(0) === "/") {
    paths = [""];
  }

  // 是否有后綴的目錄斜杠
  var trailingSlash = (request.slice(-1) === "/");

  // 第一步:如果當(dāng)前路徑已在緩存中,就直接返回緩存
  var cacheKey = JSON.stringify({request: request, paths: paths});
  if (Module._pathCache[cacheKey]) {
    return Module._pathCache[cacheKey];
  }

  // 第二步:依次遍歷所有路徑
  for (var i = 0, PL = paths.length; i < PL; i++) {
    var basePath = path.resolve(paths[i], request);
    var filename;

    if (!trailingSlash) {
      // 第三步:是否存在該模塊文件
      filename = tryFile(basePath);

      if (!filename && !trailingSlash) {
        // 第四步:該模塊文件加上后綴名,是否存在
        filename = tryExtensions(basePath, exts);
      }
    }

    // 第五步:目錄中是否存在 package.json 
    if (!filename) {
      filename = tryPackage(basePath, exts);
    }

    if (!filename) {
      // 第六步:是否存在目錄名 + index + 后綴名 
      filename = tryExtensions(path.resolve(basePath, "index"), exts);
    }

    // 第七步:將找到的文件路徑存入返回緩存,然后返回
    if (filename) {
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
 }
    
  // 第八步:沒(méi)有找到文件,返回false 
  return false;
};
3、清除緩存

根據(jù)上述的模塊引入機(jī)制我們知道,當(dāng)我們第一次引入一個(gè)模塊的時(shí)候,require的緩存機(jī)制會(huì)將我們引入的模塊加入到內(nèi)存中,以提升二次加載的性能。但是,如果我們修改了被引入模塊的代碼之后,當(dāng)再次引入該模塊的時(shí)候,就會(huì)發(fā)現(xiàn)那并不是我們最新的代碼,這是一個(gè)麻煩的事情。如何解決呢?

查看require對(duì)象

require(): 加載外部模塊

require.resolve():將模塊名解析到一個(gè)絕對(duì)路徑

require.main:指向主模塊

require.cache:指向所有緩存的模塊

require.extensions:根據(jù)文件的后綴名,調(diào)用不同的執(zhí)行函數(shù)

解決方法

    
//刪除指定模塊的緩存
delete require.cache[require.resolve("/*被緩存的模塊名稱(chēng)*/")]

// 刪除所有模塊的緩存
Object.keys(require.cache).forEach(function(key) {
     delete require.cache[key];
})

然后我們?cè)僦匦聄equire進(jìn)來(lái)需要的模塊就可以了。

參考鏈接

通過(guò)源碼解析 Node.js 中一個(gè)文件被 require 后所發(fā)生的故事

阮一峰--CommonJS規(guī)范

Nodejs源碼--GitHub

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81550.html

相關(guān)文章

  • 結(jié)合源碼分析 Node.js 模塊加載與運(yùn)行原理

    摘要:但是,對(duì)于模塊化背后的加載與運(yùn)行原理,我們是否清楚呢。源碼結(jié)構(gòu)一覽這里使用版本源碼為例子來(lái)做分析。下面就來(lái)分析的原理。至此就基本講清楚了核心模塊的加載過(guò)程。所以的內(nèi)建模塊會(huì)被放入一個(gè)叫做的數(shù)組中。 原文鏈接自我的個(gè)人博客:https://github.com/mly-zju/blog/issues/10 歡迎關(guān)注。 Node.js 的出現(xiàn),讓 JavaScript 脫離了瀏覽器的束縛,...

    W_BinaryTree 評(píng)論0 收藏0
  • 「譯」JavaScript 究竟是如何工作的?(第一部分)

    摘要:文章的第二部分涵蓋了內(nèi)存管理的概念,不久后將發(fā)布。的標(biāo)準(zhǔn)化工作是由國(guó)際組織負(fù)責(zé)的,相關(guān)規(guī)范被稱(chēng)為或者。隨著分析器和編譯器不斷地更改字節(jié)碼,的執(zhí)行性能逐漸提高。 原文地址:How Does JavaScript Really Work? (Part 1) 原文作者:Priyesh Patel 譯者:Chor showImg(https://segmentfault.com/img...

    Youngdze 評(píng)論0 收藏0
  • 2017-09-21 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選的作用鳥(niǎo)瞰前端再論性能優(yōu)化翻譯給創(chuàng)始人和們的許可協(xié)議解惑如何工作引擎深入探究?jī)?yōu)化代碼的個(gè)技巧譯文第期還是,讓我來(lái)解決你的困惑中文基礎(chǔ)為什么比快二分查找法你真的寫(xiě)對(duì)了嗎個(gè)人文章推薦機(jī)不可失直播技術(shù)盛宴,深圳騰訊開(kāi)發(fā)者大 2017-09-21 前端日?qǐng)?bào) 精選 setTimeout(fn, 0) 的作用鳥(niǎo)瞰前端 , 再論性能優(yōu)化翻譯:給創(chuàng)始人和 CTO 們的 React 許可協(xié)議...

    kidsamong 評(píng)論0 收藏0
  • Node.js 應(yīng)用建立一個(gè)更安全的沙箱環(huán)境

    摘要:當(dāng)運(yùn)行函數(shù)的時(shí)候,只能訪問(wèn)自己的本地變量和全局變量,不能訪問(wèn)構(gòu)造器被調(diào)用生成的上下文的作用域。如何建立一個(gè)更安全一些的沙箱通過(guò)上文的探究,我們并沒(méi)有找到一個(gè)完美的方案在建立安全的隔離的沙箱。 showImg(https://segmentfault.com/img/remote/1460000014575992); 有哪些動(dòng)態(tài)執(zhí)行腳本的場(chǎng)景? 在一些應(yīng)用中,我們希望給用戶(hù)提供插入自定義...

    cartoon 評(píng)論0 收藏0
  • 【阿里前端面試點(diǎn)】目標(biāo),想成為一名好的前端工程師

    摘要:廣義的定位,涉及到瀏覽器,手機(jī)里面的用戶(hù)交互展示的內(nèi)容,都屬于前端。對(duì)自己有好處因?yàn)槎啻魏桶⒗锏拿嬖嚬龠M(jìn)行了電話(huà)面試溝通,所以這些不只是一個(gè)面試官提出的問(wèn)題,而是多個(gè)面試官提出的問(wèn)題。保持一個(gè)虛心學(xué)習(xí)的狀態(tài)。 介紹 狹義的來(lái)講,前端指的就是我們常說(shuō)的html, css, javascript. 三者必不可缺. 而其中涵蓋的知識(shí)點(diǎn)不可一篇文章就能完整的講述出來(lái)的。廣義的定位,涉及到瀏覽器...

    TalkingData 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

aikin

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<