成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

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

lcodecorex / 2021人閱讀

摘要:在中,要說(shuō)如果有幾乎會(huì)在每一個(gè)文件都要用到的一個(gè)全局函數(shù)和一個(gè)全局對(duì)象,那應(yīng)該是非和莫屬了。它們是模塊機(jī)制的基石。若仍未返回,則為指定的模塊路徑依次加上,和,判斷是否存在,若存在則返回拼接后的路徑。

在 Node.js 中,要說(shuō)如果有幾乎會(huì)在每一個(gè)文件都要用到的一個(gè)全局函數(shù)和一個(gè)全局對(duì)象,那應(yīng)該是非 requiremodule.exports 莫屬了。它們是 Node.js 模塊機(jī)制的基石。大家在使用它們享受模塊化的好處時(shí),有時(shí)也不禁好奇:

為何它倆使用起來(lái)像是全局函數(shù)/對(duì)象,卻在 global 對(duì)象下訪(fǎng)問(wèn)不到它們?

"use strict"
console.log(require) // Function 
console.log(module) // Object 
console.log(global.require) // undefined
console.log(global.module) // undefined

這兩個(gè)“類(lèi)全局”對(duì)象是在什么時(shí)候,怎么生成的?

當(dāng) require 一個(gè)目錄時(shí),Node.js 是如何替我們找到具體該執(zhí)行的文件的?

模塊內(nèi)的代碼具體是以何種方式被執(zhí)行的?

循環(huán)依賴(lài)了怎么辦?

讓我們從 Node.js 項(xiàng)目的 lib/module.js 中的代碼里,細(xì)細(xì)看一番,一個(gè)文件被 require 后,具體發(fā)生的故事,從而來(lái)解答上面這些問(wèn)題。

一個(gè)文件被 require 后所發(fā)生的故事

當(dāng)我們?cè)诿钚兄星孟拢?/p>

node ./index.js

之后,src/node.cc 中的 node::LoadEnvironment 函數(shù)會(huì)被調(diào)用,在該函數(shù)內(nèi)則會(huì)接著調(diào)用 src/node.js 中的代碼,并執(zhí)行 startup 函數(shù):

// src/node.js
// ...

function startup() {
  // ...
  Module.runMain();
}

// lib/module.js
// ...

Module.runMain = function() {
  // ...
  Module._load(process.argv[1], null, true);
  // ... 
};

所以,最后會(huì)執(zhí)行到 Module._load(process.argv[1], null, true); 這條語(yǔ)句來(lái)加載模塊,不過(guò)其實(shí),這個(gè)Module._loadrequire函數(shù)的代碼中也會(huì)被調(diào)用:

// lib/module.js
// ... 

Module.prototype.require = function(path) {
  assert(path, "missing path");
  assert(typeof path === "string", "path must be a string");
  return Module._load(path, this, false);
};

所以說(shuō),當(dāng)我們?cè)诿钚兄星孟?node ./index.js,某種意義上,可以說(shuō)隨后 Node.js 的表現(xiàn)即為立刻進(jìn)行一次 require , 即:

require("./index.js")

隨后的步驟就是 require 一個(gè)普通模塊了,讓我們繼續(xù)往下看,Module._load 方法做的第一件事,便是調(diào)用內(nèi)部方法 Module._resolveFilename ,而該內(nèi)部方法在進(jìn)行了一些參數(shù)預(yù)處理后,最終會(huì)調(diào)用 Module._findPath 方法,來(lái)得到需被導(dǎo)入模塊的完整路徑,讓我們從代碼中來(lái)總結(jié)出它的路徑分析規(guī)則:

// lib/module.js
// ...

Module._findPath = function(request, paths) {
  // 優(yōu)先取緩存
  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++) {
    if (!trailingSlash) { 
      const rc = stat(basePath);
      if (rc === 0) {  // 若是文件.
        filename = toRealPath(basePath);
      } else if (rc === 1) {  // 若是目錄
        filename = tryPackage(basePath, exts);
      }

      if (!filename) {
        // 帶上 .js .json .node 后綴進(jìn)行嘗試
        filename = tryExtensions(basePath, exts);
      }
    }

    if (!filename) {
      filename = tryPackage(basePath, exts);
    }

    if (!filename) {
      // 嘗試 index.js index.json index.node
      filename = tryExtensions(path.resolve(basePath, "index"), exts);
    }

    if (filename) {
      // ...
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
  }
  return false;
};

function tryPackage(requestPath, exts) {
  var pkg = readPackage(requestPath); // 獲取 package.json 中 main 屬性的值

  // ...
  return tryFile(filename) || tryExtensions(filename, exts) ||
         tryExtensions(path.resolve(filename, "index"), exts);
}

代碼中的條件判斷十分清晰,讓我們來(lái)總結(jié)一下:

若模塊的路徑不以 / 結(jié)尾,則先檢查該路徑是否真實(shí)存在:

若存在且為一個(gè)文件,則直接返回文件路徑作為結(jié)果。

若存在且為一個(gè)目錄,則嘗試讀取該目錄下的 package.jsonmain 屬性所指向的文件路徑。

判斷該文件路徑是否存在,若存在,則直接作為結(jié)果返回。

嘗試在該路徑后依次加上 .js.json.node 后綴,判斷是否存在,若存在則返回加上后綴后的路徑。

嘗試在該路徑后依次加上 index.js , index.jsonindex.node,判斷是否存在,若存在則返回拼接后的路徑。

若仍未返回,則為指定的模塊路徑依次加上 .js.json.node 后綴,判斷是否存在,若存在則返回加上后綴后的路徑。

若模塊以 / 結(jié)尾,則嘗試讀取該目錄下的 package.jsonmain 屬性所指向的文件路徑。

判斷該文件路徑是否存在,若存在,則直接作為結(jié)果返回。

嘗試在該路徑后依次加上 .js.json.node 后綴,判斷是否存在,若存在則返回加上后綴后的路徑。

嘗試在該路徑后依次加上 index.js , index.jsonindex.node,判斷是否存在,若存在則返回拼接后的路徑。

若仍未返回,則為指定的模塊路徑依次加上 index.jsindex.jsonindex.node,判斷是否存在,若存在則返回拼接后的路徑。

在取得了模塊的完整路徑后,便該是執(zhí)行模塊了,我們以執(zhí)行 .js 后綴的 JavaScript 模塊為例。首先 Node.js 會(huì)通過(guò) fs.readFileSync 方法,以 UTF-8 的格式,將 JavaScript 代碼以字符串的形式讀出,傳遞給內(nèi)部方法 module._compile,在這個(gè)內(nèi)部方法里,則會(huì)調(diào)用 NativeModule.wrap 方法,將我們的模塊代碼包裹在一個(gè)函數(shù)中:

// src/node.js
// ...

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

NativeModule.wrapper = [
  "(function (exports, require, module, __filename, __dirname) { ",
  "
});"
];

所以,這便解答了我們之前提出的,在 global 對(duì)象下取不到它們的問(wèn)題,因?yàn)樗鼈兪且园谕獾暮瘮?shù)的參數(shù)的形式傳遞進(jìn)來(lái)的。所以順便提一句,我們平常在文件的頂上寫(xiě)的 use strict ,其實(shí)最終聲明的并不是 script-level 的嚴(yán)格模式,而都是 function-level 的嚴(yán)格模式。

最后一步, Node.js 會(huì)使用 vm.runInThisContext 執(zhí)行這個(gè)拼接完畢的字符串,取得一個(gè) JavaScript 函數(shù),最后帶著對(duì)應(yīng)的對(duì)象參數(shù)執(zhí)行它們,并將賦值在 module.exports 上的對(duì)象返回:

// lib/module.js
// ...

Module.prototype._compile = function(content, filename) {
  // ...

  var compiledWrapper = runInThisContext(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true
  });

  // ...
  const args = [this.exports, require, this, filename, dirname];
  
  const result = compiledWrapper.apply(this.exports, args);
  // ...
};

至此,一個(gè)同步的 require 操作便圓滿(mǎn)結(jié)束啦。

循環(huán)依賴(lài)

通過(guò)上文我們已經(jīng)可以知道,在 Module._load 內(nèi)部方法里 Node.js 在加載模塊之前,首先就會(huì)把傳模塊內(nèi)的 module 對(duì)象的引用給緩存起來(lái)(此時(shí)它的 exports 屬性還是一個(gè)空對(duì)象),然后執(zhí)行模塊內(nèi)代碼,在這個(gè)過(guò)程中漸漸為 module.exports 對(duì)象附上該有的屬性。所以當(dāng) Node.js 這么做時(shí),出現(xiàn)循環(huán)依賴(lài)的時(shí)候,僅僅只會(huì)讓循環(huán)依賴(lài)點(diǎn)取到中間值,而不會(huì)讓 require 死循環(huán)卡住。一個(gè)經(jīng)典的例子:

// a.js
"use strict"
console.log("a starting")
exports.done = false
var b = require("./b")
console.log(`in a, b.done=${b.done}`)
exports.done = true
console.log("a done")
// b.js
"use strict"
console.log("b start")
exports.done = false
let a = require("./a")
console.log(`in b, a.done=${a.done}`)
exports.done = true
console.log("b done")
// main.js
"use strict"
console.log("main start")
let a = require("./a")
let b = require("./b")
console.log(`in main, a.done=${a.done}, b.done=${b.done}`)

執(zhí)行 node main.js ,打印:

main start
a starting
b start
in b, a.done=false => 循環(huán)依賴(lài)點(diǎn)取到了中間值
b done
in a, b.done=true
a done
in main, a.done=true, b.done=true 
最后

由于 Node.js 中的模塊導(dǎo)入和 ES6 規(guī)范中的不同,它的導(dǎo)入過(guò)程是同步的。所以實(shí)現(xiàn)起來(lái)會(huì)方便許多,代碼量同樣也不多。十分推薦大家閱讀一下完整的實(shí)現(xiàn)。

參考:

https://github.com/nodejs/node/blob/v5.x/lib/module.js

https://github.com/nodejs/node/blob/v5.x/src/node.js

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87731.html

相關(guān)文章

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

    摘要:要想讓模塊再次運(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.exp...

    aikin 評(píng)論0 收藏0
  • Node.js 引入模塊:你所需要知道一切都在這里

    摘要:全局范圍生效,不需要。解析本地路徑首先來(lái)為你介紹對(duì)象,可以先在控制臺(tái)中看一下每一個(gè)模塊都有屬性來(lái)唯一標(biāo)示它。通常是文件的完整路徑,但是在控制臺(tái)中一般顯示成。 showImg(https://segmentfault.com/img/remote/1460000009060869?w=1794&h=648); 本文作者:Jacob Beltran 編譯:胡子大哈 翻譯原文:http:...

    aristark 評(píng)論0 收藏0
  • webpack優(yōu)化

    摘要:使用要給項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫(kù)的思想,需要完成以下事情把網(wǎng)頁(yè)依賴(lài)的基礎(chǔ)模塊抽離出來(lái),打包到一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫(kù)中去。接入已經(jīng)內(nèi)置了對(duì)動(dòng)態(tài)鏈接庫(kù)的支持,需要通過(guò)個(gè)內(nèi)置的插件接入,它們分別是插件用于打包出一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫(kù)文件。 webpack優(yōu)化 查看所有文檔頁(yè)面:全棧開(kāi)發(fā),獲取更多信息。原文鏈接:webpack優(yōu)化,原文廣告模態(tài)框遮擋,閱讀體驗(yàn)不好,所以整理成本文,方便查找。 ...

    ChanceWong 評(píng)論0 收藏0
  • webpack原理

    摘要:原理查看所有文檔頁(yè)面前端開(kāi)發(fā)文檔,獲取更多信息。初始化階段事件名解釋初始化參數(shù)從配置文件和語(yǔ)句中讀取與合并參數(shù),得出最終的參數(shù)。以上處理的相關(guān)配置如下編寫(xiě)編寫(xiě)的職責(zé)由上面的例子可以看出一個(gè)的職責(zé)是單一的,只需要完成一種轉(zhuǎn)換。 webpack原理 查看所有文檔頁(yè)面:前端開(kāi)發(fā)文檔,獲取更多信息。原文鏈接:webpack原理,原文廣告模態(tài)框遮擋,閱讀體驗(yàn)不好,所以整理成本文,方便查找。 工作...

    trigkit4 評(píng)論0 收藏0
  • nodejs源碼—初始化

    摘要:概述相信很多的人,每天在終端不止一遍的執(zhí)行著這條命令,對(duì)于很多人來(lái)說(shuō),它就像一個(gè)黑盒,并不知道背后到底發(fā)生了什么,本文將會(huì)為大家揭開(kāi)這個(gè)神秘的面紗,由于本人水平有限,所以只是講一個(gè)大概其,主要關(guān)注的過(guò)程就是模塊的初始化,和的部分基本沒(méi)有深入 概述 相信很多的人,每天在終端不止一遍的執(zhí)行著node這條命令,對(duì)于很多人來(lái)說(shuō),它就像一個(gè)黑盒,并不知道背后到底發(fā)生了什么,本文將會(huì)為大家揭開(kāi)這個(gè)...

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

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

0條評(píng)論

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