引題 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是如何獲得全部查詢路徑的呢?
模塊加載
先看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