亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 語言 > JavaScript > 正文

node.js require() 源碼解讀

2024-05-06 16:26:14
字體:
來源:轉載
供稿:網友
學習 Node.js ,必學如何使用 require 語句。本文通過源碼分析,詳細介紹 require 語句的內部運行機制,幫你理解 Node.js 的模塊機制
 

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

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

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

node.js require() 源碼解讀

一、require() 的基本用法

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

下面的內容翻譯自《Node使用手冊》。

 

復制代碼代碼如下:

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

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

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

 X 
 X.js 
 X.json 
 X.node 

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

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

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

(4) 拋出 "not found"
 

 

請看一個例子。

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

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

 

復制代碼代碼如下:

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

 

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

barbar.jsbar.jsonbar.node

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

 

復制代碼代碼如下:

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

 

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

二、Module 構造函數

了解內部邏輯以后,下面就來看源碼。

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 定義了一個構造函數 Module,所有的模塊都是 Module 的實例??梢钥吹剑斍澳K(module.js)也是 Module 的一個實例。

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

// a.jsconsole.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.jsmodule.id: .module.exports: {}module.parent: nullmodule.filename: /home/ruanyf/tmp/a.jsmodule.loaded: falsemodule.children: []module.paths: [ '/home/ruanyf/tmp/node_modules', '/home/ruanyf/node_modules', '/home/node_modules', '/node_modules' ]

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

新建另一個腳本文件 b.js,讓其調用 a.js 。

// b.jsvar a = require('./a.js');

運行 b.js 。

$ node b.jsmodule.id: /home/ruanyf/tmp/a.jsmodule.exports: {}module.parent: { object }module.filename: /home/ruanyf/tmp/a.jsmodule.loaded: falsemodule.children: []module.paths: [ '/home/ruanyf/tmp/node_modules', '/home/ruanyf/node_modules', '/home/node_modules', '/node_modules' ]

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

三、模塊實例的 require 方法

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

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

由此可知,require 并不是全局性命令,而是每個模塊提供的一個內部方法,也就是說,只有在模塊內部才能使用 require 命令(唯一的例外是 REPL 環境)。另外,require 其實內部調用 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; // 第二步:是否為內置模塊 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),以它作為模塊的識別符。然后,如果模塊已經在緩存中,就從緩存取出;如果不在緩存中,就加載模塊。

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

 

復制代碼代碼如下:

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

 

四、模塊的絕對路徑

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

Module._resolveFilename = function(request, parent) { // 第一步:如果是內置模塊,不含路徑返回 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 方法內部,又調用了兩個方法 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' ]
 

 

上面的數組,就是模塊所有可能的路徑?;旧鲜?,從當前路徑開始一級級向上尋找 node_modules 子目錄。最后那三個路徑,主要是為了歷史原因保持兼容,實際上已經很少用了。

有了可能的路徑以后,下面就是 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;};

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

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

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) { // 模塊源碼});

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

(完)



注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
97国产精品视频人人做人人爱| 久久资源免费视频| 欧美日韩一区二区免费在线观看| 91亚洲精华国产精华| 国产精品美女网站| 亚洲精品aⅴ中文字幕乱码| 日韩欧美福利视频| 96pao国产成视频永久免费| 国产日韩欧美在线播放| 亚洲国产日韩欧美在线99| 国产精品免费久久久久久| 亚洲iv一区二区三区| 国产精品露脸自拍| 久久香蕉精品香蕉| 日韩精品免费在线播放| 国产在线拍揄自揄视频不卡99| 日韩国产中文字幕| 久久人人看视频| 国产精品久久一区| 国产日韩欧美影视| 国产精品高潮呻吟久久av野狼| 亚洲精品国产拍免费91在线| 久久精品色欧美aⅴ一区二区| 亚洲福利视频二区| 日韩精品一区二区三区第95| 久久久亚洲国产| 亚洲国产精品久久91精品| 欧美精品电影在线| 精品国产自在精品国产浪潮| 日韩av影视在线| 91视频九色网站| 国产精品18久久久久久首页狼| 日韩亚洲第一页| 久久精品国产69国产精品亚洲| 成人免费观看49www在线观看| 九九热这里只有精品免费看| 91国产美女在线观看| 日韩国产精品亚洲а∨天堂免| 日韩一区二区福利| 国产精品欧美一区二区三区奶水| 综合网日日天干夜夜久久| 日韩精品一区二区视频| 国产成人精品一区二区三区| 亚洲一区二区中文字幕| 国产精品电影网站| 91在线视频免费| 国产亚洲成精品久久| 国产精品久久久久久久久久99| 91久久国产精品| 欧美日韩美女在线| 日韩av快播网址| 这里只有精品视频| 大量国产精品视频| 国产91色在线| 亚洲高清在线观看| 日韩av免费看网站| 国产亚洲精品久久久久久| 日韩一区视频在线| 久久亚洲精品成人| 精品亚洲国产视频| 日韩免费观看av| 韩国精品久久久999| 欧美色道久久88综合亚洲精品| 国产视频精品va久久久久久| 亚洲人成网站免费播放| 视频一区视频二区国产精品| 91香蕉嫩草神马影院在线观看| 97视频免费在线观看| 国产精品成人国产乱一区| 国内精品一区二区三区四区| 成人免费网站在线| 欧美成人精品h版在线观看| 深夜福利亚洲导航| 韩国视频理论视频久久| 在线观看欧美视频| 久久综合久中文字幕青草| 热草久综合在线| 日韩电影免费在线观看| 日韩在线www| 亚洲欧美日韩一区在线| 日韩电影免费观看中文字幕| 久久久国产成人精品| 成人激情电影一区二区| 国产精品一区二区三区久久| 91国语精品自产拍在线观看性色| 欧美一级大片视频| 亚洲精品国产精品国自产观看浪潮| 岛国av一区二区在线在线观看| 精品日本美女福利在线观看| 久久精品视频99| 91免费观看网站| 久久免费成人精品视频| 国产成人鲁鲁免费视频a| 国产suv精品一区二区| 欧美综合在线第二页| 亚洲a中文字幕| 91亚洲国产精品| 欧美成人性生活| 成人激情视频在线播放| 亚洲91精品在线| 欧美日韩一区二区三区在线免费观看| 欧美激情一区二区三区久久久| 国产成人在线亚洲欧美| 一区二区欧美亚洲| 国产精品自在线| 欧美日韩在线视频观看| 亚洲91精品在线| 2021国产精品视频| 欧洲s码亚洲m码精品一区| 日韩av电影在线免费播放| 亚洲图片欧洲图片av| 91色精品视频在线| 色综合久久久久久中文网| 欧美日韩亚洲高清| 久久久精品免费视频| 性欧美在线看片a免费观看| 欧美一级免费视频| 91亚洲国产成人精品性色| 性欧美xxxx视频在线观看| 国产精品99久久99久久久二8| 国内精品小视频在线观看| 国产精品久久婷婷六月丁香| 国产91精品视频在线观看| 国产日韩在线看| 国产精品偷伦一区二区| 久久影视电视剧免费网站清宫辞电视| 欧美日韩国产一区中文午夜| 91精品久久久久久久久中文字幕| 亚洲最大福利网站| 亚洲偷欧美偷国内偷| 久久久久久亚洲精品| 国产精品综合网站| 久久中文久久字幕| 国产欧美一区二区三区在线看| 色诱女教师一区二区三区| 国产精品久久久久久中文字| 国产日本欧美视频| 国产精品国模在线| 91视频免费网站| 久久久亚洲精品视频| 粗暴蹂躏中文一区二区三区| 自拍偷拍亚洲在线| 久久久之久亚州精品露出| 久久综合伊人77777| 国产精品国模在线| 国产欧美日韩免费| 亚洲国产精品一区二区三区| 国产欧美在线观看| 91精品国产高清久久久久久久久| 日韩av大片免费看| 有码中文亚洲精品| 在线丨暗呦小u女国产精品| 日韩在线观看av| 久久久久久久亚洲精品| 日韩一二三在线视频播| 在线免费观看羞羞视频一区二区| 国产一区二区三区网站| 亚洲精品美女久久久| 欧美大成色www永久网站婷| 亚洲精品一区中文字幕乱码| 欧美激情精品久久久久久| 精品国产成人在线| 97av在线播放| www.色综合|