【前端知識體系-NodeJS相關】對NodeJS模塊機制的理解

1. CommonJS模塊規範

1.1 模塊引用

var math = require('math');

1.2 模塊定義

[!NOTE]
上下文提供exports對象用於導出當前模塊的方法和變量,而且他是惟一的導出出口前端

exports其實是module.exports,而module.exports就是以一個暴露給外部的對象。node

  • 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個步驟json

  • 路徑分析
  • 文件定位
  • 編譯執行

在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文件處理

[!NOTE]
每個編譯成功的模塊都會將其文件路徑索引緩存在Module._cache對象上,以提升二次引入性能性能

2.3.1 js模塊的編譯

[!NOTE]
在編譯的過程當中,Node對獲取的JS文件進行了頭尾包裝。這也是每一個模塊都能訪問到 require、exports、module、__filename、__dirname的緣由ui

(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. 核心模塊

[!NOTE]
Node的核心模塊在編譯成可執行文件的過程當中被編譯進了二進制文件。核心模塊其實分爲C/C++編寫的和Javascript編寫的兩部分,其中C/C++文件存放在Node項目的src目錄下,Javascript文件存放在lib目錄下。

  1. C/C++拓展模塊

  2. 模塊調用棧

  3. 先後端公用模塊

  4. 模塊側重點
  • 前端瓶頸在於帶寬,後端瓶頸在於CPU和內存等資源。前端須要經過網絡加載代碼,後端則從磁盤加載,兩者加載速度再也不同一量級上。

  • node的模塊引入幾乎都是同步的,但前端模塊如果也採用同步方式來引入必會在用戶體驗上形成很大的問題,即UI初始化實際過長

4. AMD規範

Asynchronous Moudle Definition 「異步模塊定義」, AMD須要在聲明的時候指定全部的依賴,經過形參傳遞依賴到模塊內容中。

定義以下

define(id?, dependencies, factory);

5. CMD 規範

與AMD主要區別在於定於模塊與依賴引入部分。

CMD支持動態引入

define(funtion(require, exports, moudle) {
  // The module code goes here
})
相關文章
相關標籤/搜索