這是我讀《深刻淺出Nodejs》的筆記,真是但願個人機械鍵盤快點到啊,累死我了。javascript
主要分爲模塊引用、模塊定義、模塊標識三個部分。java
上下文提供require()方法來引入外部模塊,示例代碼以下:node
//test.js //引入一個模塊到當前上下文中 var math = require('math'); math.add(1, 2);
上下文提供了exports對象用於導入導出當前模塊的方法或者變量,而且它是惟一的導出出口。模塊中存在一個module對象,它表明模塊自身,exports是module的屬性。一個文件就是一個模塊,將方法做爲屬性掛載在exports上就能夠定義導出的方式:json
//math.js exports.add = function () { var sum = 0, i = 0, args = arguments, l = args.length; while(i < l) { sum += args[i++]; } return sum; }
這樣就可像test.js裏那樣在require()以後調用模塊的屬性或者方法了。數組
模塊標識就是傳遞給require()方法的參數,它必須是符合小駝峯命名的字符串,或者以"."、".."開頭的相對路徑或者絕對路徑,能夠沒有文件後綴名".js".瀏覽器
在Node中引入模塊,須要經歷以下三個步驟:緩存
在Node中模塊分爲兩類:一是Node提供的模塊,稱爲核心模塊;另外一類是用戶編寫的模塊,稱爲文件模塊。
核心模塊是Node源碼在編譯過程當中編譯進了二進制執行文件。在Node啓動時這些模塊就被加載進內存中,因此核心模塊引入時省去了文件定位和編譯執行兩個步驟,而且在路徑分析中優先判斷,所以核心模塊的加載速度是最快的。工具
文件模塊則是在運行時動態加載,速度比核心模塊慢。性能
和瀏覽器會緩存靜態js文件同樣,Node也會對引入的模塊進行緩存,不一樣的是瀏覽器緩存的是文件,Node緩存的是編譯執行以後的對象。
require()對相同模塊的二次加載一概採用緩存優先的方式,這是第一優先級的,核心模塊緩存檢查先於文件模塊的緩存檢查。ui
由於標識符有幾種形式,對於不一樣的標識符,模塊的查找和定位有不一樣程度上的差別。
模塊標識符在Node中主要分爲如下幾類:
核心模塊優先級僅次於緩存加載,所以沒法加載一個和核心模塊標識符相同的自定義模塊。
以"."、".."開頭和"/"開始的標識符,這裏都被看成文件模塊來處理。require()方法會將路徑轉爲真實路徑,並以真實路徑做爲索引,並將編譯執行後的結果存放到緩存中。
自定義模塊是指非核心模塊,也不是路徑形式的標識符。它是一種特殊的文件模塊,多是一個文件或者包的形式。
模塊路徑是Node在定位文件模塊的具體文件時制定的查找策略,具體表現爲一個路徑組成的數組(module.paths)。這個路徑由當前目錄開始往上一直到根目錄,Node會逐個嘗試模塊路徑中的路徑,直到找到目標文件未知,若到達根目錄仍是沒有找到目標文件,則會拋出查找失敗的異常。當前文件的目錄越深,模塊查找耗時越多。
調用require()方法時若參數沒有文件擴展名,Node會按.js、.json、.node的順尋補足擴展名,依次嘗試。
在嘗試過程當中,須要調用fs模塊阻塞式地判斷文件是否存在。由於Node是單線程的,這是一個會引發性能問題的地方。若是是.node或者.json文件能夠加上擴展名加快一點速度。另外一個訣竅是:同步配合緩存。
require()分析文件擴展名後,可能沒有查到對應文件,而是找到了一個目錄,此時Node會將目錄看成一個包來處理。
首先, Node在擋牆目錄下查找package.json,經過JSON.parse()解析出包秒速對象,從中取出main屬性指定的文件名進行定位。若main屬性指定文件名錯誤,或者沒有pachage.json文件,Node會將index看成默認文件名。
若目錄分析沒有定位成功任何文件,則自定義模塊進入下一個模塊路徑。
每一個模塊文件模塊都是一個對象,它的定義以下:
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; if(parent && parent.children) { parent.children.push(this); } this.filename = null; this.loaded = false; this.children = []; }
對於不一樣擴展名,其載入方法也有所不一樣:
每個編譯成功的模塊都會將其文件路徑做爲索引緩存在Module._cache對象上。
.json文件調用的方法以下:
//Native extension for .json Module._extensions['.json'] = function(module, filename) { var content = NativeModule.require('fs').readFileSync(filename, 'utf-8'); try { module.exports = JSON.parse(stripBOM(content)); } catch(err) { err.message = filename + ':' + err.message; throw err; } }
Module._extensions會被賦值給require()的extensions屬性,因此能夠用:console.log(require.extensions);
輸出系統中已有的擴展加載方式。
固然也能夠本身增長一些特殊的加載:require.extensions['.txt'] = function(){//code};
。
可是從v0.10.6版本開始官方不鼓勵經過這種方式自定義擴展名加載,而是指望先將其餘語言或文件編譯成JavaScript文件後再加載,這樣的好處在於不講煩瑣的編譯加載等過程引入Node的執行過程。
在編譯的過程當中,Node對獲取的javascript文件內容進行了頭尾包裝,將文件內容包裝在一個function中:
(function (exports, require, module, _filename, _dirname) { //js文件內容 });
包裝以後的代碼會經過vm原生模塊的runInThisContext()方法執行(具備明確上下文,不污染全局),返回一個具體的function對象,最後傳參執行,執行後返回model.exports.
核心模塊分爲C/C++編寫和JavaScript編寫的兩個部分,其中C/C++文件放在Node項目的src目錄下,JavaScript文件放在lib目錄下。
Node採用了V8附帶的js2c.py工具,將全部內置的JavaScript代碼轉換成C++裏的數組,生成node_natives.h頭文件:
namespace node { const char node_native[] = { 47, 47, ..}; const char dgram_native[] = { 47, 47, ..}; const char console_native = { 47, 47, ..}; const char buffer_native = { 47, 47, ..}; const char querystring_native = { 47, 47, ..}; const char punycode_native = { 47, 47, ..}; ... struct _native { const char* name; const char* source; size_t source_len; } static const struct _native natives[] = { { "node", node_native, sizeof(node_native)-1}, { "dgram", dgram_native, sizeof(dgram_native)-1}, ... }; }
在這個過程當中,JavaScript代碼以字符串形式存儲在node命名空間中,是不可直接執行的。在啓動Node進程時,js代碼直接加載到內存中。在加載的過程當中,js核心模塊經歷標識符分析後直接定位到內存中。
lib目錄下的模塊文件也在引入過程當中經歷了頭尾包裝的過程,而後才執行和導出了exports對象。與文件模塊的區別在於:獲取源代碼的方式(核心模塊從內存加載)和緩存執行結果的位置。
js核心模塊源文件經過process.binding('natives')取出,編譯成功的模塊緩存到NativeModule._cache上。代碼以下:
function NativeModule() { this.filename = id + '.js'; this.id = id; this.exports = {}; this.loaded = fales; } NativeModule._source = process.binding('natives'); NativeModule._cache = {};