深刻淺出 Node ( 二 ) 模塊機制

模塊機制

1、CommonJS出現背景

js實現編寫後端程序的不足之處前端

1. 沒有模塊系統(js一個先天不足就是模塊功能)node

2. ECMAScript僅僅定義了js的核心庫,可是對於文件系統、IO系統等卻沒有標準的API。HTML5雖然在一直致力於推動標準化,可是這些標準耶都是前端的。c++

3. 沒有標準接口,沒有定義過服務器或者數據庫的接口。git

4. 缺少包管理系統,沒有自動安裝和管理依賴的能力。github

而CommonJS的出現正好彌補了沒有標準的這一個缺點。

CommonJS 規範了 模塊二進制Buffer二進制I/0,進程環境文件系統web服務器網管接口包管理等web

2、CommonJS和Node的關係

Node的出現離不開CommonJS規範的影響,而CommonJS能以一種獨尋常的姿態出如今各大公司的代碼中,離不開Node優異的表現。數據庫

圖片描述

3、CommonJS的模塊規範

1. 模塊引入npm

var http = require("http");

2. 模塊導出json

// math.js
exports.add = function(){
    ...
}
// program.js
Var math = require('math.js');

exports.increament = function(val){
    return math.add(val);
}

3. 模塊標識後端

模塊標識就是require()的括號中的參數,必須是小駝峯結構,能夠是相對路徑也能夠是絕對路徑,還能夠沒有後綴名。


4、Node的模塊實現

node 的模塊實現其實是借鑑了CommonJS的部分,並非所有照搬,並且也增長了一些本身須要的東西進去。

1. 模塊引入

node中模塊引入須要通過:

1. 路徑分析
2. 文件定位
3. 編譯執行

2. 模塊分類

1. 核心模塊 Node提供的
2. 文件模塊 用戶編寫的

3. 核心模塊

核心模塊部分在Node源碼加載中就編譯完畢了,編譯成了二進制執行文件,在Node進程啓動後,部分核心模塊就直接加載進內存中,因此能夠不用`文件定位`和`編譯`,所以部分核心模塊的加載時最快的。

4. 文件模塊

文件模塊則是在運行時動態加載,要通過完整的路徑分析、文件定位、編譯執行,一次通常比較慢。

5. 緩衝加載

node對二次加載的核心模塊,一概採用的時緩衝優先的原則。

6. 路徑分析和文件定位

模塊標識符分析
  • 核心模塊
核心模塊的加載僅次於緩衝加載
  • 路徑形式的文件模塊
首先將其轉換爲真實的路徑,而後以真實路徑做爲索引,將其編譯後放進緩衝中,等待調用。
因爲是經過確切的文件地址找到的,因此須要花費必定的時間,
  • 自定義模塊
這是非核心模塊,也不是路徑形式的標識,它是一種特殊的文件模塊,多是一個文件或者是一個包。這種查找是最費時間的。首先須要知道一下/模塊路徑/的概念/

> 例子

好比你要加載一個包,這個包放在了node_modules文件夾下,你要引入的話能夠不以路徑的形式寫,能夠是隻寫名稱。(也就是引入一個本身npm的包)

console.log(module.paths);
// 會獲得下面的數組
[ 'D:\\myweb\\node\\node\\module\\node_modules',
  'D:\\myweb\\node\\node\\node_modules',
  'D:\\myweb\\node\\node_modules',
  'D:\\myweb\\node_modules',
  'D:\\node_modules' ]

這就是模塊路徑了,查找機制是: 首先在當前路徑下找是否有node_modules文件夾下的該包,若是沒有就查找上一層目錄,依次類推,直到根目錄下的node_modules,若是依舊沒有找到,那麼就報錯了。

// 顯然這種狀況就是致使它速度較慢的主要緣由了,(查找的路徑越深就越慢),可是咱們能夠經過一些小技巧來經可能的減小這種狀況哦~~
文件定位

文件定位中還須要注意的包括有文件擴展名目錄包的處理

node 的模塊引入的時候是能夠不寫擴展名的,node會按照js json node的順序來分析。依次嘗試。因爲嘗試使用的是node中fs模塊的同步文件查找,所以可能會致使阻塞狀況發生,所以這裏咱們須要注意兩個小技巧了:

小技巧:

1. json node文件最好加擴展名

2. 同步配合緩衝能夠環節Node單線程阻塞調用的缺陷

另外,若是沒有找到對應的文件,確實找到了一個目錄,那麼將會將其看成是一個包來處理了。

如何在這個包下找到咱們須要引入的入口文件對呢?

1. 首先找是否含有package.json,若是有,則分析它的main屬性,找到main屬性對應的那個文件。

2. 若是沒有package.json或者是main解析失敗了,那麼就找文件名爲index的文件,依次從index.js index.json index.node查找。

3. 若是在該目錄下依舊沒有找到,那麼就查找寫一個匹配的目錄,若是仍然沒有找到,那麼就報錯了。


6. 模塊編譯

node中每個模塊都是一個對象,當定位到一個文件的時候,node就會將其包裝成一個module對象,而後根據不一樣的文件名,其載入方法也不一樣的。

  1. js文件: 經過fs的同步讀取文件來執行
  2. json文件,經過fs同步讀取文件,用Json.parse()來獲得對象,將其賦值給Module.exports
  3. node文件,這事c/c++編寫的擴展文件,經過Process.dlopen()方法來加載執行。(不須要編譯)

圖片描述


7. module.exports和exports的區別和聯繫

exports = module.exports = {};的區別就和 var a = {}; b = a;的區別同樣.

首先要明確的一點,module是一個對象 {Object}。
當你新建一個文件,好比mo.js,文件內容以下:

console.log(module);

而後在CMD裏執行這個文件node mo.js,就能看到module實際上是一個Module實例,你能夠這麼理解,NodeJS中定義了一個Module類,這個類中有不少屬性和方法,exports是其中的一個屬性:

function Module {
  id : 'blabla',
  exports : {},
  blabla...
}

當每個文件被執行或者時require的時候,node就會建立一個module實例。var module = new Module();

console.log(module); //你會看到Module中的exports爲空對象{}
module.exports = {
  print : function(){console.log(12345)}
}
console.log(module); //你會看到Module中的exports對象已經有了print()方法

module.exports 其實就是module實例中的module添加方法或者屬性。

console.log(module); //你會看到Module中的exports爲空對象{}
console.log(exports); //你會看到Module中的exports爲空對象{}
module.exports = {
  print : function(){console.log(12345)}
}
console.log(module); //你會看到Module中的exports對象有了print()方法
exports.name = '小白妹妹';
console.log(module); //你會看到Module中的exports對象不只有了print()方法,還有了name屬性

不難看出exports其實就是module.exports的一個引用。

// 你能夠這麼理解
var module = new Module();
var exports = module.exports;

當你require的時候,返回的就是module.exports的內容。

// 經常使用場景分析
module.exports.name = '小白妹妹';
exports.age = 10;
module.exports.print = function(){console.log(12345)};
//若是隻是添加屬性和方法,二者能夠混用。

// 也能夠
module.exports = {
  name = '小白妹妹';
};
exports.age = 10;
module.exports.print = function(){console.log(12345)};

// 【X】可是不能夠
module.exports = {
  name = '小白妹妹';
};
exports = {age:10}; // exports如今是{age:10}這個對象的引用,再也不是module.exports的引用了
console.log(module); //你會看到Module的exports中只有name屬性!!!

// 【X】
exports.age = 10; 
console.log(module); //你會看到Module的exports中多了age屬性
module.exports = {
   name = '小白妹妹';
};
// 直接改變了module.exports的引用,以前的屬性值也被覆蓋掉了。
console.log(module); //你會看到Module的exports中仍是隻有name屬性!!!

總結

  1. 改變exports的指向後所添加的exports.xxx都是無效的。由於require返回的只會是module.exports
  2. 不能在使用了exports.xxx以後,改變module.exports的指向。由於exports.xxx添加的屬性和方法並不存在於module.exports所指向的新對象中。
  3. 對於要導出的屬性,能夠簡單直接掛到exports對象上
  4. 對於類,爲了直接使導出的內容做爲類的構造器可讓調用者使用new操做符建立實例對象,應該把構造函數掛到module.exports對象上,不要和導出屬性值混在一塊兒

相關文章
相關標籤/搜索