1.為什么要CommonJS規(guī)范
javascript存在的缺點
- 沒有模塊系統(tǒng)
- 標準庫比較少
- 沒有標準接口
- 缺乏包管理系統(tǒng)
CommonJS規(guī)范的提出,彌補了javascript沒有標準的缺陷,以達到像Python、Ruby、Java具備的開發(fā)大型應用的基礎能力,這樣javascript不僅僅能在客戶端應用還能開發(fā)以下應用:
- 服務端應用
- 命令行工具
- 桌面圖形界面應用
- 混合應用
2.CommonJS的模塊規(guī)范
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()的參數(shù)必須是符合小駝峰命名的字符串,或以.、..開頭的相對路徑,或絕對路徑,可以是沒有.js后綴的js文件。
模塊中定義的全局變量只作用于該文件內(nèi)部,不污染其他模塊。
4.Node模塊實現(xiàn)
Node中引入模塊需經(jīng)歷以下步驟:
- 路徑分析
- 文件定位
- 編譯執(zhí)行
Node中模塊分為兩類: 1.Node提供的 "核心模塊",2.用戶編寫的 "文件模塊"。
核心模塊Node源碼編譯時已經(jīng)編譯成二進制執(zhí)行文件,Node啟動時直接加載進內(nèi)存中,不需要文件定位和編譯執(zhí)行兩個步驟,且在路徑分析中優(yōu)先判斷,加載速度最快。
1.優(yōu)先從緩存加載
Node會對引入過的模塊進行緩存,核心模塊和文件模塊相同的模塊在二次加載時一律從緩存優(yōu)先加載(第一優(yōu)先級),核心模塊緩存檢測優(yōu)先于文件模塊緩存檢測。
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中每個文件模塊都是一個對象。
編譯和執(zhí)行是引入文件模塊的最后一個階段,定位到一個文件后,Node會新建一個模塊對象,然后根據(jù)路徑載入并編譯。不同擴展名載入方式:
- .js 通過fs模塊讀取后編譯執(zhí)行
- .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規(guī)范的實現(xiàn)。
2.C/C++模塊編譯
Node調(diào)用process.dlopen()來進行加載執(zhí)行,windows和*nix平臺下dlopen()通過不同方式實現(xiàn),通過libuv兼容層進行封裝。
3.JSON文件編譯
Node使用fs模塊讀取json文件內(nèi)容,使用JSON.parse()得到對象然后給他賦給模塊對象的exports屬性。
4.核心模塊
核心模塊分為C/C++編寫和javascript編寫,C/C++存放在Node項目的src文件下,javascript文件存在lib目錄下。
核心模塊中有些模塊核心部分使用C/C++完成其他使用javascript實現(xiàn)包裝導出。由純C/C++編寫的部分稱為內(nèi)建模塊,例:buffer、crypto、evals、fs、os等模塊部分使用C/C++編寫。
依賴層關系: 內(nèi)建模塊(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內(nèi)部庫
- 其他庫
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 存放可執(zhí)行位二進制文件
- lib 存放javascript文件
- doc 存放文檔
- test 存放單元測試
7.前后端公用模塊
1.AMD規(guī)范
AMD規(guī)范是CommonJS規(guī)范的一個延伸,定義模塊方法:
define(id?, dependencies?, factory);
define(function() {
let exports = {};
exports.sayHello = () => {
console.log(`hello form module: ${module.id}`);
}
return exports;
})
2.CMD規(guī)范
CMD與AMD規(guī)范的主要區(qū)別在于定義模塊和依賴引入的部分。AMD需要在聲明的時候指定所有依賴,通過形參傳遞依賴到模塊中:
define(['dep1', 'dep2'], function() {
return function() {}
})
于AMD規(guī)范相比,CMD模塊更接近與Node對CommonJS規(guī)范的定義:
define(factory);
在依賴部分,CMD支持動態(tài)引入:
define(function(require, exports, module) {
// module code
})
require,exports,module通過形參傳遞給模塊,在需要依賴模塊時隨時調(diào)用require()引入。
兼容多種模塊規(guī)范
((name, definition) => {
//檢測是否為AMD或者CMD
let hasDefine = typeof define === 'function',
//檢測是否為Node
hasExports = typeof module !== 'undefined' && 'module.exports';
if(hasDefine) {
//AMD或CMD環(huán)境
define(definition);
}else if(hasExports) {
//定義為普通Node模塊
module.exports = definition();
}else {
//將模塊執(zhí)行結果掛載在window對象下
this[name] = definition;
}
})('hello', function() {
let hello = () => {};
return helllo;
})