1.為什麼要CommonJS規範
javascript存在的缺點
- 沒有模組系統
- 標準庫比較少
- 沒有標準介面
- 缺乏包管理系統
CommonJS規範的提出,彌補了javascript沒有標準的缺陷,以達到像Python、Ruby、Java具備的開發大型應用的基礎能力,這樣javascript不僅僅能在客戶端應用還能開發以下應用:
- 服務端應用
- 命令列工具
- 桌面圖形介面應用
- 混合應用
2.CommonJS的模組規範
1.模組引入
使用require()來引入 ,接受一個模組標識。
let math = require('math');
複製程式碼
2.定義模組
上下文提供裡exports物件用於匯出模組或變數,並且是唯一匯出出口。在模組中存在一個module物件,代表模組自身,exports是它的一個屬性。在nodejs中一個檔案就是一個模組,把方法掛在exports物件上作為屬性即可定義匯出
//math.js
exports.add = function(){
let sum = 0,
i = 0,
args = arguments,
l = args.length;
while(i < l) {
sum += args[i ++];
}
return sum;
}
複製程式碼
在另一個檔案require使用
const math = require('./math');
let res = math.add(1, 2, 3);
console.log(res)
//6
複製程式碼
3.模組標識
模組標識為require()的引數必須是符合小駝峰命名的字串,或以.、..開頭的相對路徑,或絕對路徑,可以是沒有.js字尾的js檔案。
模組中定義的全域性變數只作用於該檔案內部,不汙染其他模組。
4.Node模組實現
Node中引入模組需經歷以下步驟:
- 路徑分析
- 檔案定位
- 編譯執行
Node中模組分為兩類: 1.Node提供的 "核心模組",2.使用者編寫的 "檔案模組"。
核心模組Node原始碼編譯時已經編譯成二進位制執行檔案,Node啟動時直接載入進記憶體中,不需要檔案定位和編譯執行兩個步驟,且在路徑分析中優先判斷,載入速度最快。
1.優先從快取載入
Node會對引入過的模組進行快取,核心模組和檔案模組相同的模組在二次載入時一律從快取優先載入(第一優先順序),核心模組快取檢測優先於檔案模組快取檢測。
2.路徑分析檔案定位
1.模組識別符號分析
識別符號分類:
- 核心模組,如http、fs、path等
- .或..開始的相對路徑檔案模組
- 以/開頭的絕對路徑模組
- 非路徑形式的檔案模組,如自定義的connect模組 一個檔案或一個包
2.自定義模組
console.log(module.paths)
//[ 'c:\\Users\\maikuraki\\Desktop\\nodejs\\node_modules',
'c:\\Users\\maikuraki\\Desktop\\node_modules',
'c:\\Users\\maikuraki\\node_modules',
'c:\\Users\\node_modules',
'c:\\node_modules' ]
複製程式碼
Node會逐個路徑嘗試知道找到目標檔案,模組路徑越深耗時越多。
3.檔案定位
識別符號可以不包含檔案擴充套件,這種情況下Node會安裝.js、.json、.node次序補全副檔名。
如果是個包Node會檢測裡面的package.json檔案Node通過JOSN.parse()解析出包的描述物件去除main屬性指向的檔案進行定位,如果沒有該屬性預設查詢index.js、index.json、index.node。
3.模組編譯
在Node中每個檔案模組都是一個物件。
編譯和執行是引入檔案模組的最後一個階段,定位到一個檔案後,Node會新建一個模組物件,然後根據路徑載入並編譯。不同副檔名載入方式:
- .js 通過fs模組讀取後編譯執行
- .node 這是C/C++編寫的擴充套件檔案,通過dlopen()方法載入最後編譯生成檔案
- .json 通過fs模組讀取檔案使用JSON.parse()解析並返回
- 其他副檔名檔案 當做.js檔案載入
1.javascript模組的編譯
在編譯過程中Node對獲取的javascript檔案進行的頭尾包裝
(function(exports, require, module, __filename, __dirname) {
exports.add = (x, y) => {
return x + y;
}
})
複製程式碼
這樣每個模組檔案直接都進行了作用域隔離,這就是Node對CommonJS規範的實現。
2.C/C++模組編譯
Node呼叫process.dlopen()來進行載入執行,windows和*nix平臺下dlopen()通過不同方式實現,通過libuv相容層進行封裝。
3.JSON檔案編譯
Node使用fs模組讀取json檔案內容,使用JSON.parse()得到物件然後給他賦給模組物件的exports屬性。
4.核心模組
核心模組分為C/C++編寫和javascript編寫,C/C++存放在Node專案的src檔案下,javascript檔案存在lib目錄下。 核心模組中有些模組核心部分使用C/C++完成其他使用javascript實現包裝匯出。由純C/C++編寫的部分稱為內建模組,例:buffer、crypto、evals、fs、os等模組部分使用C/C++編寫。
依賴層關係: 內建模組(C/C++) ---> 核心模組(javascript)---> 檔案模組
核心模組的引入流程 以os原生模組引入為例
NODE_MODULE(node_os,reg_func) ---> get_builtin_module('node_os') ---> process.binding('os') ---> NativeModule.require('os') ---> require('os')
5.C/C++擴充套件模組
1.擴充套件模組在不同平臺上編譯和載入過程
Windows
C/C++原始碼 ---> VC++ --編譯原始碼--> .dll檔案 --生成.node檔案--> 載入.dll檔案 --dlopen()載入--> 匯出給javascript使用
*nix
C/C++原始碼 ---> g++/gcc --編譯原始碼--> .so檔案 --生成.node檔案--> 載入.so檔案 --dlopen()載入--> 匯出給javascript使用
2.編譯條件
- node-gyp工具
- V8引擎C++庫
- libuv庫
- Node內部庫
- 其他庫
3.C/C++擴充套件模組的載入
require()引入.node檔案過程
javascript(require('./hello.node')) ---> 原生模組(process.dlopen('./hello.node',exports)) ---> libuv(uv_dlopen()/uv_dlsym()) ---> [{*nix: dlopen()/dlsym(), Windows : loadLibraryExW()/GetProcAddress()}]
6.包與NPM
包結構:
- package.json 包描述檔案
- bin 存放可執行位二進位制檔案
- lib 存放javascript檔案
- doc 存放文件
- test 存放單元測試
7.前後端公用模組
1.AMD規範
AMD規範是CommonJS規範的一個延伸,定義模組方法:
define(id?, dependencies?, factory);
define(function() {
let exports = {};
exports.sayHello = () => {
console.log(`hello form module: ${module.id}`);
}
return exports;
})
複製程式碼
2.CMD規範
CMD與AMD規範的主要區別在於定義模組和依賴引入的部分。AMD需要在宣告的時候指定所有依賴,通過形參傳遞依賴到模組中:
define(['dep1', 'dep2'], function() {
return function() {}
})
複製程式碼
於AMD規範相比,CMD模組更接近與Node對CommonJS規範的定義:
define(factory);
複製程式碼
在依賴部分,CMD支援動態引入:
define(function(require, exports, module) {
// module code
})
複製程式碼
require,exports,module通過形參傳遞給模組,在需要依賴模組時隨時呼叫require()引入。
相容多種模組規範
((name, definition) => {
//檢測是否為AMD或者CMD
let hasDefine = typeof define === 'function',
//檢測是否為Node
hasExports = typeof module !== 'undefined' && 'module.exports';
if(hasDefine) {
//AMD或CMD環境
define(definition);
}else if(hasExports) {
//定義為普通Node模組
module.exports = definition();
}else {
//將模組執行結果掛載在window物件下
this[name] = definition;
}
})('hello', function() {
let hello = () => {};
return helllo;
})
複製程式碼