nodejs筆記-模塊機制

1.爲何要CommonJS規範

javascript存在的缺點javascript

  • 沒有模塊系統
  • 標準庫比較少
  • 沒有標準接口
  • 缺少包管理系統

CommonJS規範的提出,彌補了javascript沒有標準的缺陷,以達到像Python、Ruby、Java具有的開發大型應用的基礎能力,這樣javascript不單單能在客戶端應用還能開發如下應用:java

  • 服務端應用
  • 命令行工具
  • 桌面圖形界面應用
  • 混合應用

2.CommonJS的模塊規範

1.模塊引入

使用require()來引入 ,接受一個模塊標識。node

let math = require('math');

2.定義模塊

上下文提供裏exports對象用於導出模塊或變量,而且是惟一導出出口。在模塊中存在一個module對象,表明模塊自身,exports是它的一個屬性。在nodejs中一個文件就是一個模塊,把方法掛在exports對象上做爲屬性便可定義導出json

//math.js
exports.add = function(){
    let sum = 0,
        i = 0,
        args = arguments,
        l = args.length;
    while(i < l) {
        sum += args[i ++];
    }
    return sum;
}

在另外一個文件require使用windows

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;
})
相關文章
相關標籤/搜索