你知道require是什麼嗎?

引題     javascript

     用過node的同窗應該都知道require是用來加載模塊的,那你是否存在以下的疑問呢? html

1. require(path)是如何依據path找到對應module呢? java

2. 爲什麼在模塊定義中,必定要經過module.exports暴漏出接口?module.exports與require存在什麼關係node

     對上述問題進行歸納能夠抽象出以下兩個問題:git

1. module的路徑分析
2. 文件加載github

 

切入json

   首先來直觀地看看require是什麼? api

// node環境下執行:
console.log(require.toString)

//輸入結果爲:
'function require(path) {\n    return self.require(path);\n  }'

  上述代碼說明require函數僅僅是module.require的封裝,這樣就須要查看node中的module源代碼了。數組

 

加載模塊的方式閉包

       首先來直觀來認識一下node的模塊加載方式有哪些方式:

case 1:

// 'path'爲node的核心模塊
var path = require('path')

case2:

// a.js,路徑爲: basePath/a.js
var myModule = require('./my-module')
// my-module的路徑爲basePath/node_modules/myModule.js

case 3:

// a.js, 路徑: basePath/a.js
var main = require('./')
// basePath下還包括package.json, index.js

 

路徑解析

     在node的官方API中,咱們能夠找到這段描述:

To get the exact filename that will be loaded when require() is called, use the require.resolve() function.

Putting together all of the above, here is the high-level algorithm in pseudocode of what require.resolve does:

 ......

     試試在node環境下用用require.resolve這個API:

require.resolve('./a.js')
// 這樣就獲得a.js的絕對路徑

     爲了探索原因,就從node核心代碼中的mdoule.js找答案吧: 

require.resolve = function(request) {
  return Module._resolveFilename(request, self);
}
	
Module._resolveFilename = function(request, parent) {
  // 判斷是否爲node的核心模塊
  if (NativeModule.exists(request)) {
     return request;
   }
   // 獲得查詢路徑,格式爲數組:[id, [paths]]
   var resolvedModule =  Module._resolveLookupPaths(request, parent);	  	                 

   var paths = resolvedModule[1];
  // 根據path、fileName獲得絕對路徑
  var filename = Module._findPath(request, paths);
   return filename;
}    

     那Module._resolveLookupPaths是如何獲得全部查詢路徑的呢?

  1. 爲node的核心模塊,stop
  2. 以./或../開頭,本地查找, stop
  3. 沿着文件樹,獲得node_module的全部路徑,直到/node_modules,在node_module中查找,stop
  4. path爲目錄,則檢查package.json文件是否存在main屬性,不然默認爲index.js
  5. 最後返回new Error('Cannot find module"' + request + '"');

 

模塊加載

      先看require的源代碼:

// 咱們常常使用的require函數
function require(path) {
    return self.require(path);
}
// 調用_load函數,加載所需的模塊
Module.prototype.require = function(path) {
  return Module._load(path, this);
}

     這樣模塊函數的調用鏈接到了Module._load函數:

Module.cache = {};
Module._load = function() {
  // 檢測模塊是否已經加載過
  var cachedModule = Module._cache[filename];
   if (cachedModule) {
     return cachedModule.exports;
   }
   // 模塊還未加載,則爲模塊建立module實例
   var module = new Module(filename, parent);
  // 新建立的實例存儲於cache中
   Module._cache[filename] = module;
   // 開始獲取模塊的內容
   module.load(filename);
   // 對外提供接口
   return module.exports;
}

  接下來問題的關鍵就變成了module.load,該方法用於獲取module的內容,而後進行解析:

Module.prototype.load = function(filename) {
  // 解析出文件的後綴, 存在['.js', '.json', 'node']三種後綴
  var extension = path.extname(filename) || '.js';
  // 根據後綴,獲取相關的模塊
  Module._extensions[extension](this, filename);
}

  node會匹配按照.js、.json、.node三種格式進行模塊匹配,根據文件類型的不一樣採起不一樣的加載策略,可是以實際開發中以加載.js最多,該種策略最後須要調用Module.prototype._compile進行編譯處理:

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

Module.prototype._compile = function(content, filename) {
  //將內容放入到(function() { content }),造成閉包,建立私有做用域
  var wrapper = Module.wrap(content);
  // bind新的執行上下文
  var compiledWrapper = runInThisContext(wrapper, { filename: filename });
  // 向外暴漏接口:module.exports, require, module,__filename,  __dirname, 
  var args = [self.exports, require, self, filename, dirname];
   return compiledWrapper.apply(self.exports, args);
}

  這樣,咱們就能夠在require來獲取相應地module。

 

結論
      node如今這麼火,各類優點鋪天蓋地涌來,會讓剛剛入行的人以爲深不可測,於是每每會讓人望而卻步。可是隻要咱們勇於突破第一步,深刻下來仔細分析,就會發現其實沒有那麼晦澀難懂,踏出第一步真的很關鍵!

 

參考資料

http://thenodeway.io/posts/get-fancy/how-require-actually-works/

https://github.com/joyent/node/blob/master/lib/module.js

http://nodejs.org/api/modules.html

https://github.com/substack/node-resolve

相關文章
相關標籤/搜索