對NodeJS模組機制的理解
1. Commonjs模組規範
1.1 模組引用
var math = require('math');
1.2 模組定義
上下文提供exports物件用於匯出當前模組的方法和變數,並且他是唯一的匯出出口,exports實際上是module.exports,而module.exports就是以一個暴露給外部的物件。
- exports.some就是給這個物件上新增屬性
- 直接使用 module.exports = {...} 則可以讓外部直接獲取到這個物件,相當與為exports換了一個引用,如果在這之前使用exports.some會把之前的覆蓋
1.3 Commonjs用法
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
1.4 原理
var module = require('./a.js')
module.a
// 這裡其實就是包裝了一層立即執行函式,這樣就不會汙染全域性變量了,
// 重要的是 module 這裡,module 是 Node 獨有的一個變數
module.exports = {
a: 1
}
// module 基本實現
var module = {
id: 'xxxx', // 我總得知道怎麼去找到他吧
exports: {} // exports 就是個空物件
}
// 這個是為什麼 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
// 匯出的東西
var a = 1
module.exports = a
return module.exports
};
// 然後當我 require 的時候去找到獨特的
// id,然後將要使用的東西用立即執行函式 包裝下,over
2. Node的模組實現
在Node中引入模組,需要經歷3個步驟
- 路徑分析
- 檔案定位
- 編譯執行
在node中,模組分為兩類:一類是node提供的模組稱為核心模組,一類是使用者編寫的成為檔案模組。
-
核心模組在編譯中編譯成了二進位制檔案。在Node程序啟動時,部分核心模組就被直接載入入記憶體。所以這部分核心模組引入時就省了檔案定位和編譯執行這兩個步驟,並且在路徑分析中優先判斷,它的載入速度是最快的。
-
檔案模組是執行時動態載入。需要完整的路徑分析、檔案定位、編譯執行
2.1 優先從快取載入
Node對引入的模組都回進行快取,而且快取的是編譯執行後的物件。 不管是核心模組還是檔案模組,require()都一律採用快取優先的方式。
2.2 路徑分析和檔案定位
2.2.1 模組識別符號分析
- 核心模組
- 路徑形式的檔案模組
- 自定義模組
- node_modules下
- 查詢最費時
2.2.2 檔案定位
- 檔案拓展名分析
- 如果省略拓展名,回按 .js .node .json的次序依次嘗試
- 如果.node .json的話,加上拓展名會加快一點速度
- 同步配合快取,可大幅緩解單執行緒中阻塞式呼叫的缺陷
- 目錄分析和包
- 如果沒有檔名,會將Index當作預設檔名
2.3 模組編譯
- .js檔案
- 通過fs同步讀取後編譯執行
- .node
- 這是用C/C++編寫的拓展檔案,通過dlopen()方法載入最後編譯生成的檔案
- .json
- 用JSON.parse()解析返回結果
- 其餘拓展名
- 當作.js檔案處理
每一個編譯成功的模組都會將其檔案路徑索引快取在Module._cache物件上,以提高二次引入效能
2.3.1 js模組的編譯
在編譯的過程中,Node對獲取的JS檔案進行了頭尾包裝。這也是每個模組都能訪問到 require、exports、module、__filename、__dirname的原因
(funciton(exports, require, module, __filename, __dirname) {
/* 自己寫的程式碼 */
});
這樣使得模組檔案間都進行了作用域隔離,不用擔心變數汙染全域性。
為moudle.exports賦值,exports物件是通過形參的方式傳入,直接賦值形參會改變形參的引用,但並不能改變作用域外的值。
exports = function() {
// my class
}
var change = function(a) {
a = 100;
}
var a = 10;
change(a);
console.log(a); // => 10
如果要達到require引入一個類的效果,請賦值給 module.exports物件。這個迂迴的方案不改變形參的引用。
2.3.2 C/C++ 模組的編譯
Node呼叫process.dlopen()方法進行載入和執行。
實際上 .node模組並不需要編譯,因為它是編寫C/C++模組之後編譯生成的,所以這裡只有載入和執行的過程。在執行的過程中,模組exports物件與.node模組產生練習,然後返回給呼叫者。
3. 核心模組
Node的核心模組在編譯成可執行檔案的過程中被編譯進了二進位制檔案。核心模組其實分為C/C++編寫的和JavaScript編寫的兩部分,其中C/C++檔案存放在Node專案的src目錄下,JavaScript檔案存放在lib目錄下。
-
C/C++拓展模組
-
模組呼叫棧
-
前後端公用模組
- 模組側重點
-
前端瓶頸在於頻寬,後端瓶頸在於CPU和記憶體等資源。前端需要通過網路載入程式碼,後端則從磁碟載入,二者載入速度不再同一量級上。
-
node的模組引入幾乎都是同步的,但前端模組若是也採用同步方式來引入必會在使用者體驗上造成很大的問題,即UI初始化實際過長
資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com
4. AMD規範
Asynchronous Moudle Definition “非同步模組定義”, AMD需要在宣告的時候指定所有的依賴,通過形參傳遞依賴到模組內容中。
定義如下
define(id?, dependencies, factory);
5. CMD 規範
與AMD主要區別在於定於模組與依賴引入部分。
CMD支援動態引入
define(funtion(require, exports, moudle) {
// The module code goes here
})