最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

node.js require() 源碼解讀

 更新時間:2015年12月13日 13:01:47   作者:阮一峰  
學習 Node.js ,必學如何使用 require 語句。本文通過源碼分析,詳細介紹 require 語句的內(nèi)部運行機制,幫你理解 Node.js 的模塊機制

2009年,Node.js 項目誕生,所有模塊一律為 CommonJS 格式。

時至今日,Node.js 的模塊倉庫 npmjs.com ,已經(jīng)存放了15萬個模塊,其中絕大部分都是 CommonJS 格式。

這種格式的核心就是 require 語句,模塊通過它加載。學習 Node.js ,必學如何使用 require 語句。本文通過源碼分析,詳細介紹 require 語句的內(nèi)部運行機制,幫你理解 Node.js 的模塊機制。

一、require() 的基本用法

分析源碼之前,先介紹 require 語句的內(nèi)部邏輯。如果你只想了解 require 的用法,只看這一段就夠了。

下面的內(nèi)容翻譯自《Node使用手冊》。

復制代碼 代碼如下:

當 Node 遇到 require(X) 時,按下面的順序處理。

(1)如果 X 是內(nèi)置模塊(比如 require('http'))
  a. 返回該模塊。
   b. 不再繼續(xù)執(zhí)行。

(2)如果 X 以 "./" 或者 "/" 或者 "../" 開頭
   a. 根據(jù) X 所在的父模塊,確定 X 的絕對路徑。
   b. 將 X 當成文件,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續(xù)執(zhí)行。

 X
 X.js
 X.json
 X.node

  c. 將 X 當成目錄,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續(xù)執(zhí)行。

 X/package.json(main字段)
 X/index.js
 X/index.json
 X/index.node

(3)如果 X 不帶路徑
   a. 根據(jù) X 所在的父模塊,確定 X 可能的安裝目錄。
   b. 依次在每個目錄中,將 X 當成文件名或目錄名加載。

(4) 拋出 "not found"

請看一個例子。

當前腳本文件 /home/ry/projects/foo.js 執(zhí)行了 require('bar') ,這屬于上面的第三種情況。Node 內(nèi)部運行過程如下。

首先,確定 x 的絕對路徑可能是下面這些位置,依次搜索每一個目錄。

復制代碼 代碼如下:

/home/ry/projects/node_modules/bar
/home/ry/node_modules/bar
/home/node_modules/bar
/node_modules/bar

搜索時,Node 先將 bar 當成文件名,依次嘗試加載下面這些文件,只要有一個成功就返回。

bar
bar.js
bar.json
bar.node

如果都不成功,說明 bar 可能是目錄名,于是依次嘗試加載下面這些文件。

復制代碼 代碼如下:

bar/package.json(main字段)
bar/index.js
bar/index.json
bar/index.node

如果在所有目錄中,都無法找到 bar 對應的文件或目錄,就拋出一個錯誤。

二、Module 構造函數(shù)

了解內(nèi)部邏輯以后,下面就來看源碼。

require 的源碼在 Node 的 lib/module.js 文件。為了便于理解,本文引用的源碼是簡化過的,并且刪除了原作者的注釋。

function Module(id, parent) {
 this.id = id;
 this.exports = {};
 this.parent = parent;
 this.filename = null;
 this.loaded = false;
 this.children = [];
}

module.exports = Module;

var module = new Module(filename, parent);

上面代碼中,Node 定義了一個構造函數(shù) Module,所有的模塊都是 Module 的實例。可以看到,當前模塊(module.js)也是 Module 的一個實例。

每個實例都有自己的屬性。下面通過一個例子,看看這些屬性的值是什么。新建一個腳本文件 a.js 。

// a.js

console.log('module.id: ', module.id);
console.log('module.exports: ', module.exports);
console.log('module.parent: ', module.parent);
console.log('module.filename: ', module.filename);
console.log('module.loaded: ', module.loaded);
console.log('module.children: ', module.children);
console.log('module.paths: ', module.paths);

運行這個腳本。

$ node a.js

module.id: .
module.exports: {}
module.parent: null
module.filename: /home/ruanyf/tmp/a.js
module.loaded: false
module.children: []
module.paths: [ '/home/ruanyf/tmp/node_modules',
 '/home/ruanyf/node_modules',
 '/home/node_modules',
 '/node_modules' ]

可以看到,如果沒有父模塊,直接調(diào)用當前模塊,parent 屬性就是 null,id 屬性就是一個點。filename 屬性是模塊的絕對路徑,path 屬性是一個數(shù)組,包含了模塊可能的位置。另外,輸出這些內(nèi)容時,模塊還沒有全部加載,所以 loaded 屬性為 false 。

新建另一個腳本文件 b.js,讓其調(diào)用 a.js 。

// b.js

var a = require('./a.js');

運行 b.js 。

$ node b.js

module.id: /home/ruanyf/tmp/a.js
module.exports: {}
module.parent: { object }
module.filename: /home/ruanyf/tmp/a.js
module.loaded: false
module.children: []
module.paths: [ '/home/ruanyf/tmp/node_modules',
 '/home/ruanyf/node_modules',
 '/home/node_modules',
 '/node_modules' ]

上面代碼中,由于 a.js 被 b.js 調(diào)用,所以 parent 屬性指向 b.js 模塊,id 屬性和 filename 屬性一致,都是模塊的絕對路徑。

三、模塊實例的 require 方法

每個模塊實例都有一個 require 方法。

Module.prototype.require = function(path) {
 return Module._load(path, this);
};

由此可知,require 并不是全局性命令,而是每個模塊提供的一個內(nèi)部方法,也就是說,只有在模塊內(nèi)部才能使用 require 命令(唯一的例外是 REPL 環(huán)境)。另外,require 其實內(nèi)部調(diào)用 Module._load 方法。

下面來看 Module._load 的源碼。

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

 // 計算絕對路徑
 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);
 }

 // 第三步:生成模塊實例,存入緩存
 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;
};

上面代碼中,首先解析出模塊的絕對路徑(filename),以它作為模塊的識別符。然后,如果模塊已經(jīng)在緩存中,就從緩存取出;如果不在緩存中,就加載模塊。

因此,Module._load 的關鍵步驟是兩個。

復制代碼 代碼如下:

◾Module._resolveFilename() :確定模塊的絕對路徑
◾module.load():加載模塊

四、模塊的絕對路徑

下面是 Module._resolveFilename 方法的源碼。

Module._resolveFilename = function(request, parent) {

 // 第一步:如果是內(nèi)置模塊,不含路徑返回
 if (NativeModule.exists(request)) {
  return request;
 }

 // 第二步:確定所有可能的路徑
 var resolvedModule = Module._resolveLookupPaths(request, parent);
 var id = resolvedModule[0];
 var paths = resolvedModule[1];

 // 第三步:確定哪一個路徑為真
 var filename = Module._findPath(request, paths);
 if (!filename) {
  var err = new Error("Cannot find module '" + request + "'");
  err.code = 'MODULE_NOT_FOUND';
  throw err;
 }
 return filename;
};

上面代碼中,在 Module.resolveFilename 方法內(nèi)部,又調(diào)用了兩個方法 Module.resolveLookupPaths() 和 Module._findPath() ,前者用來列出可能的路徑,后者用來確認哪一個路徑為真。

為了簡潔起見,這里只給出 Module._resolveLookupPaths() 的運行結果。

復制代碼 代碼如下:

[   '/home/ruanyf/tmp/node_modules',
    '/home/ruanyf/node_modules',
    '/home/node_modules',
    '/node_modules'
    '/home/ruanyf/.node_modules',
    '/home/ruanyf/.node_libraries',
     '$Prefix/lib/node' ]

上面的數(shù)組,就是模塊所有可能的路徑。基本上是,從當前路徑開始一級級向上尋找 node_modules 子目錄。最后那三個路徑,主要是為了歷史原因保持兼容,實際上已經(jīng)很少用了。

有了可能的路徑以后,下面就是 Module._findPath() 的源碼,用來確定到底哪一個是正確路徑。

Module._findPath = function(request, paths) {

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

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

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

 // 第一步:如果當前路徑已在緩存中,就直接返回緩存
 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;
  }
 }

 // 第八步:沒有找到文件,返回false 
 return false;
};

經(jīng)過上面代碼,就可以找到模塊的絕對路徑了。

有時在項目代碼中,需要調(diào)用模塊的絕對路徑,那么除了 module.filename ,Node 還提供一個 require.resolve 方法,供外部調(diào)用,用于從模塊名取到絕對路徑。

require.resolve = function(request) {
 return Module._resolveFilename(request, self);
};

// 用法
require.resolve('a.js')
// 返回 /home/ruanyf/tmp/a.js

五、加載模塊

有了模塊的絕對路徑,就可以加載該模塊了。下面是 module.load 方法的源碼。

Module.prototype.load = function(filename) {
 var extension = path.extname(filename) || '.js';
 if (!Module._extensions[extension]) extension = '.js';
 Module._extensions[extension](this, filename);
 this.loaded = true;
};

上面代碼中,首先確定模塊的后綴名,不同的后綴名對應不同的加載方法。下面是 .js 和 .json 后綴名對應的處理方法。

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

Module._extensions['.json'] = function(module, filename) {
 var content = fs.readFileSync(filename, 'utf8');
 try {
  module.exports = JSON.parse(stripBOM(content));
 } catch (err) {
  err.message = filename + ': ' + err.message;
  throw err;
 }
};

這里只討論 js 文件的加載。首先,將模塊文件讀取成字符串,然后剝離 utf8 編碼特有的BOM文件頭,最后編譯該模塊。

module._compile 方法用于模塊的編譯。

Module.prototype._compile = function(content, filename) {
 var self = this;
 var args = [self.exports, require, self, filename, dirname];
 return compiledWrapper.apply(self.exports, args);
};

上面的代碼基本等同于下面的形式。

(function (exports, require, module, __filename, __dirname) {
 // 模塊源碼
});

也就是說,模塊的加載實質(zhì)上就是,注入exports、require、module三個全局變量,然后執(zhí)行模塊的源碼,然后將模塊的 exports 變量的值輸出。

(完)

相關文章

  • Nodejs libuv運行原理詳解

    Nodejs libuv運行原理詳解

    在本篇文章里小編給大家整理的是關于Nodejs libuv運行原理以及相關知識點,有需要的朋友們可以學習下。
    2019-08-08
  • node.js+jQuery實現(xiàn)用戶登錄注冊AJAX交互

    node.js+jQuery實現(xiàn)用戶登錄注冊AJAX交互

    本篇文章主要介紹了用Node.js當作后臺、jQuery寫前臺AJAX代碼實現(xiàn)用戶登錄和注冊的功能的相關知識。具有很好的參考價值。下面跟著小編一起來看下吧
    2017-04-04
  • 淺談Node.js爬蟲之網(wǎng)頁請求模塊

    淺談Node.js爬蟲之網(wǎng)頁請求模塊

    本篇文章主要介紹了淺談Node.js爬蟲之網(wǎng)頁請求模塊,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 如何在node環(huán)境實現(xiàn)“get數(shù)據(jù)解析”代碼實例

    如何在node環(huán)境實現(xiàn)“get數(shù)據(jù)解析”代碼實例

    這篇文章主要介紹了如何在node環(huán)境實現(xiàn)“get數(shù)據(jù)解析”代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-07-07
  • 詳解nodejs中express搭建權限管理系統(tǒng)

    詳解nodejs中express搭建權限管理系統(tǒng)

    本篇文章主要介紹了詳解express搭建權限管理系統(tǒng),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Node.js?express中的身份認證的實現(xiàn)

    Node.js?express中的身份認證的實現(xiàn)

    本文主要介紹了Node.js?express中的身份認證的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • Node.js檢測端口(port)是否被占用的簡單示例

    Node.js檢測端口(port)是否被占用的簡單示例

    大家有沒有遇到過在開啟本地服務時,有這么一種情況:當前端口已經(jīng)被另一個項目使用了,導致服務開啟失敗。那么接下來,我們通過簡簡單單的示例代碼來檢測端口是否已經(jīng)被占用。有需要的朋友們可以參考借鑒。
    2016-09-09
  • nodejs利用readline提示輸入內(nèi)容實例代碼

    nodejs利用readline提示輸入內(nèi)容實例代碼

    這篇文章主要給大家介紹了關于nodejs利用readline提示輸入內(nèi)容的相關資料,Readline是Node.js里實現(xiàn)標準輸入輸出的封裝好的模塊,通過這個模塊我們可以以逐行的方式讀取數(shù)據(jù)流,需要的朋友可以參考下
    2021-07-07
  • NodeJs搭建本地服務器之使用手機訪問的實例講解

    NodeJs搭建本地服務器之使用手機訪問的實例講解

    今天小編就為大家分享一篇NodeJs搭建本地服務器之使用手機訪問的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Nodejs?http模塊返回內(nèi)容中文亂碼問題及解決

    Nodejs?http模塊返回內(nèi)容中文亂碼問題及解決

    這篇文章主要介紹了Nodejs?http模塊返回內(nèi)容中文亂碼問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評論

财经| 丹巴县| 山阴县| 临夏县| 凌云县| 北海市| 巴林左旗| 中方县| 鲁甸县| 青州市| 津南区| 敖汉旗| 含山县| 慈溪市| 宜春市| 皮山县| 桓仁| 综艺| 澄城县| 宁波市| 灵丘县| 旬阳县| 武安市| 勃利县| 霸州市| 西城区| 桦川县| 沅江市| 蚌埠市| 从化市| 荃湾区| 武宁县| 平湖市| 交城县| 遵化市| 淅川县| 海伦市| 德令哈市| 伊宁市| 分宜县| 象山县|