你知道require是什么吗?
引題
用過node的同學應該都知道require是用來加載模塊的,那你是否存在如下的疑問呢?
1. require(path)是如何依據path找到對應module呢?
2. 為何在模塊定義中,一定要通過module.exports暴漏出接口?module.exports與require存在什么關系
對上述問題進行概括可以抽象出如下兩個問題:
1. module的路徑分析
2. 文件加載
切入
首先來直觀地看看require是什么?
// node環境下執行:
console.log(require.toString)
//輸入結果為:
'function require(path) {
return self.require(path);
}'
上述代碼說明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是如何得到所有查詢路徑的呢?
為node的核心模塊,stop
以./或../開頭,本地查找, stop
沿著文件樹,得到node_module的所有路徑,直到/node_modules,在node_module中查找,stop
path為目錄,則檢查package.json文件是否存在main屬性,否則默認為index.js
最后返回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
總結
以上是生活随笔為你收集整理的你知道require是什么吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1-1、作用域深入和面向对象
- 下一篇: pt-archiver使用