nodejs筆記-模組機制

mai-kuraki發表於2018-01-30

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中引入模組需經歷以下步驟:

  1. 路徑分析
  2. 檔案定位
  3. 編譯執行

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;
})
複製程式碼

相關文章