深刻淺出Node.js學習筆記(二)

模塊機制

Web1.0時代,JavaScript腳本語言的兩個主要功能:前端

  1. 表單驗證;
  2. 網頁特效;

Web2.0時代,前端工程師利用JavaScript大大提高了網頁的用戶體驗,經歷了工具類庫、組件庫、前端框架、前端應用的變遷。node

JavaScript的先天缺陷:模塊express

高級語言的模塊化機制:npm

  1. Java-類文件;
  2. Python-import機制;
  3. Ruby-require;
  4. PHP-include和require;

1.CommonJS規範

commonJS的願景:但願JavaScript可以在任何地方運行。json

1.1CommonJS的出發點

JavaScript規範的缺陷:後端

  1. 沒有模塊系統;
  2. 標準庫較少;
  3. 沒有標準接口;
  4. 缺少包管理系統;

CommonJS規範中,CommonJSAPI能夠編寫的應用:數組

  1. 服務端JavaScript應用程序;
  2. 命令行工具;
  3. 桌面圖形界面應用程序;
  4. 混合應用;

1.2CommonJS的模塊規範

  1. 模塊引用瀏覽器

    採用require()方法;緩存

    var math = require('math');
  2. 模塊定義安全

    require():用來引入外部模塊;

    exports:導出模塊的方法或變量,惟一導出的出口;

    module:表明模塊自身;

    // math.js
    exports.add = function (){
        var sum = 0,
        i = 0,
        args = arguments,
        l = args.lenght;
        while(i<l){
            sum += args[i++];
        }
        return sum;
    }
    
    //program.js
    var math = require("math");
    exports.increment = function (val){
        return math.add(val,1);
    }
  3. 模塊標識

    模塊標識:

    就是傳遞給require()方法的參數,符合小駝峯命名的字符串,或者以...開頭的相對路徑或絕對路徑,能夠沒有後綴.js。

2.Node的模塊實現

Node中引入模塊經歷的三個步驟:

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

Node中,模塊分爲兩種:

  1. 核心模塊(Node提供的模塊);

    核心模塊部分在Node源代碼的編譯過程當中,編譯了二進制執行文件。在Node進程啓動時,部分核心模塊被直接加載進內存中,因此這部分核心模塊引入時,文件定位和編譯執行兩個步驟能夠省略掉,而且在路徑分析中優先判斷,它的加載速度是最快的。

  2. 文件模塊(用戶編寫的模塊);

    文件模塊在運行時動態加載,須要完整的路徑分析、文件定位、編譯執行過程,速度比核心模塊慢。

2.1優先從緩存加載

與前端瀏覽器會緩存靜態腳本文件以提升性能同樣,Node對引入過的模塊都會進行緩存,以減小二次引入的開銷。不一樣的是,瀏覽器僅緩存文件,而Node緩存的是編譯和執行後的對象。

不管是核心模塊仍是文件模塊,require()方法對相同模塊的二次加載都一概採用緩存優先的方式,這是第一優先級的。不一樣之處在於核心模塊的緩存檢查先於文件模塊的緩存檢查。

2.2路徑分析和文件定位

  1. 模塊標識符分析

    模塊標識符在Node中的分類:

    1. 核心模塊,如http、fs、path等;
    2. ...開始的相對路徑文件模塊;
    3. 以/開始的絕對文件模塊;
    4. 非路徑形式的文件模塊,如自定義的connect模塊;

核心模塊

核心模塊的優先級僅次於緩存加載,在Node的源代碼編譯過程當中,已經編譯爲二進制代碼了,其加載過程最快。

路徑形式的文件模塊

在分析路徑模塊時,require()將路徑轉化爲真實路徑,以真實路徑做爲索引,將編譯執行後的結果放在緩存中,以使二次加載更快,其加載速度慢於核心模塊。

自定義模塊

這類模塊的查找最費時,也是全部方式最慢的一種。

模塊路徑:

Node在定位文件模塊的具體文件時制定的查找策略,具體表現爲一個路徑組成的數組。

  1. 文件定位

    從緩存加載的優化策略使得二次引入時不須要路徑分析、文件定位和編譯執行的過程,大大提升了再次加載模塊時的效率。

    文件定位過程當中,須要注意的細節,包括文件擴展名的分析、目錄和包的處理。

    文件擴展名分析

    require()在分析標識符的過程當中,出現標識符中不包含文件擴展名的狀況,Node會按.js、.json、.node的次序補足擴展名,依次嘗試。

    在嘗試的過程當中,須要調用fs模塊同步阻塞式低判斷文件是否存在。

    目錄分析和包

    在分析標識的過程當中,require()經過分析文件擴展名以後,可能沒有查找到對應的文件,但卻獲得一個目錄,此時Node會將目錄當作一個包來處理。

2.3模塊編譯

在Node中,每一個文件模塊都是一個對象。

編譯和執行時引入文件模塊的最後一個階段。定位到具體的文件後,Node會新建一個模塊對象,而後根據路徑載入並編譯。對於不一樣的文件擴展名,載入方法也有所不一樣。

  • .js文件;經過fs模塊同步讀取文件後編譯執行。
  • .node文件;這是用C/C++編寫的擴展文件,經過dlopen()方法加載最後編譯生成的文件。
  • .json文件;經過fs模塊同步讀取文件後,用JSON.parse()解析返回結果。
  • 其他擴展名文件;都被當作.js文件載入。

3.核心模塊

核心模塊分爲兩部分:

  1. C/C++編寫的部分;
  2. JavaScript編寫的部分;

3.1JavaScript核心模塊的編譯過程

JavaScript核心模塊的編譯過程:

  1. 轉存爲C/C++代碼;

    在此過程當中,JavaScript代碼已字符串的形式存儲在Node的命名空間中,是不可直接執行的。在啓動Node進程時,JavaScript代碼直接加載進內存中。在加載的過程當中,JavaScript核心模塊經歷標識符分析後,直接定位到內存中,比普通的文件模塊從磁盤中一處一處查找要快得多。

  2. 編譯JavaScript核心模塊;

    在引入JavaScript核心模塊的過程當中,也經歷了頭尾包裝的過程,而後才執行和導出exports對象。與文件模塊區別的地方在於:獲取源碼的方式(核心模塊從內存中加載)以及緩存執行結果的位置。

3.2C/C++核心模塊的編譯過程

C/C++模塊主內完成核心,JavaScript主外實現封裝的模式是Node可以提升性能的常見方式。

  1. 內建模塊的組織形式;

    內建模塊的優點:

    1. C/C++編寫,性能優於腳本語言;
    2. 在進行文件編譯時,被編譯二進制文件。一旦Node開始執行,直接被加載進內存,無須再次作標識定位,文件定位,編譯等過程,直接可執行。
  2. 內建模塊的導出;

    在Node的全部模塊類型中,存在一種依賴層級關係:

    文件模塊可能依賴核心模塊,核心模塊可能依賴內建模塊。

3.3核心模塊的引入流程

核心模塊的引入流程經歷了C/C++層面的內建模塊的定義,(JavaScript)核心模塊的定義和引入以及(JavaScript)文件模塊層面的引入。

3.4編寫核心模塊

核心模塊被編譯進二進制文件須要遵循必定規則。做爲Node的使用者,幾乎沒有機會參與核心模塊的開發。

4.C/C++擴展模塊

JavaScript的一個典型的弱點就是位運算。

在JavaScript應用中,會頻繁出現位運算的需求,包括轉換、編碼等過程,經過JavaScript實現,CPU資源會耗費不少。

4.1前提條件

  1. GYP項目生成工具;
  2. V8引擎C++庫;
  3. libux庫;
  4. Node內部庫;
  5. 其餘庫;

4.2C/C++擴展模塊的編寫

普通的擴展模塊與內建模塊的區別在於無須將源代碼編譯進Node,而是經過dlopen()方式動態加載。

4.3C/C++擴展模塊的編譯

經過GYP工具實現。

4.4C/C++擴展模塊的加載

require()方法經過解析標識符、路徑分析、文件定位,而後加載執行便可。

C/C++擴展模塊與JavaScript模塊的區別在於加載以後不須要編譯,子類執行以後就能夠被外部調用了,其加載速度比JavaScript模塊速度略快。

使用C/C++擴展模塊的一個好處在於能夠更加靈活和動態地加載它們,保持Node模塊自身簡單性的同時,給予Node五=無限的可能性。

5.模塊調用棧

C/C++內建模塊:屬於最底層的模塊,屬於核心模塊,主要經過API給JavaScript核心模塊和第三方JavaScript文件模塊的調用。

JavaScript核心模塊的兩個職責:

  1. 做爲C/C++內建模塊的封裝層和橋接層,供文件模塊調用;
  2. 純粹的功能模塊,不須要和底層打交道;

文件模塊:一般由第三方編寫,包括普通JavaScript模塊的C/C++擴展模塊,主要調用方向爲普通JavaScript模塊調用擴展模塊。

模塊調用棧

6.包與NPM

包和NPM是將模塊聯繫起來的一種機制。

包組織模塊示意圖:

包組織模塊示意圖

CommonJS包的定義:

由包結構和包描述文件兩個部分組成,前者用於組織包中的各類文件,後者用於描述包的相關信息,以供外部讀取分析。

6.1包結構

包實際是一個存檔文件,即一個目錄直接打包爲.zip和tar.gz格式的文件,安裝後解壓還原爲目錄。

包目錄包含的文件:

  1. package.json:包描述文件;
  2. bin:用於存放可執行二進制文件的目錄;
  3. lib:用於存放JavaScript代碼的目錄;
  4. doc:用於存放文檔的目錄;
  5. test:用於存放單元測試用例的代碼;

6.2包描述文件和NPM

包描述文件用於表達非代碼相關的信息,是個JSON格式的文件-package.json,位於包的根目錄下,是包的重要組成部分。

6.3NPM經常使用功能

對於Node而言,NPM幫助完成了第三方模塊的發佈、安裝和依賴等。藉助Node與第三方模塊之間造成了很好的一個生態系統。

  1. 查看幫助;

    查看當前NPM版本:

    $ npm -v

    查看幫助:

    $ npm
  2. 安裝依賴包

    安裝依賴包是NPM最多見的用法,執行語句:

    $ npm install express

    1.全局模式安裝

    $ npm install express -g

    2.從本地安裝

    本地安裝只需爲NPM指明package.json文件的所在的位置便可。它能夠是一個包含package.json的存檔文件,也能夠是個URL地址,也能夠是個目錄有package.json的目錄的位置。

    3.從非官方源安裝

    從非官方安裝,能夠經過鏡像源安裝。

    $ npm config set underscore --registry=http://registry.url

    鏡像源安裝指定默認源:

    $ npm config set registry http://registry.url
  3. NPM鉤子命令

    C/C++模塊其實是編譯後才能使用的。package.json中script字段的提出就是讓包在安裝或者卸載等過程當中提供鉤子機制。

  4. 發佈包

    1. 編寫模塊;
    2. 初始化包描述文件;

      $ npm init
    3. 註冊包倉庫帳號

      $ npm adduser
    4. 上傳包

      $ npm publish .
    5. 安裝包

      $ npm install hello_test_jackson --registy=http://npmjs.org
    6. 管理包權限

      多人進行發佈

      $ npm owner ls eventproxy
  5. 分析包

    在使用NPM的過程當中,或許你不能確認當前目錄下可否經過require()順利引入想要的包,執行npm ls分析包。

    $ npm ls

6.4局域NPM

爲了同時可以享受NPM上衆多的包,同時對本身的包進行保密和限制,現有的解決方案就是企業搭建本身的NPM倉庫。

企業混合使用官方倉庫和局域倉庫的示意圖:

混合使用官方倉庫和局域倉庫的示意圖

對於企業內部而言,私有的可重用模塊能夠打包到局域NPM倉庫,這樣能夠保持更新的中心化,不至於讓各個小項目各自維護相同功能的模塊,杜絕經過複製粘貼實現代碼共享的行爲。

6.5NPM潛在問題

NPM的潛在問題:

  1. 每一個人均可以分享包平臺上,鑑於開發人員水平不一,上面的包的質量也參差不齊;
  2. Node代碼能夠運行在服務器端,須要考慮安全問題;

對於包的使用者而言,包質量和安全問題須要做爲是否採納模塊的一個判斷條件。

如何評判一個包的安全和質量?

  1. 開源社區內在的健康發展機制-口碑效應;
  2. Github中,模塊項目的觀察者數量和分支數量;
  3. 包中測試用例和文檔的情況;

Kwalitee模塊的考察點:

  1. 具有良好的測試;
  2. 具有良好的文檔(README、API);
  3. 具有良好的測試覆蓋率;
  4. 具有良好的編碼規範;
  5. 更多條件;

7.先後端共用模塊

7.1模塊的側重點

先後端JavaScript分別擱置在HTTP的兩端,它們扮演的角色並不一樣。

瀏覽器端的JavaScript:

須要經歷從同一個服務器端分發到多個客戶端執行,瓶頸在於帶寬,須要網絡加載代碼;

瀏覽器端的JavaScript:

相同的代碼須要屢次執行,瓶頸在於CPU和內存等資源,從磁盤中加載代碼;

二者的加載的速度不在一個數量級別。

CommonJS爲後端JavaScript制定的規範;

AMD爲前端JavaScript制定的規範;

7.2AMD規範

AMD規範是CommonJS模塊規範的一個延伸,定義以下;

define(id?,dependenceies?,factory)

模塊的id和依賴是可選的,

與Node模塊類似之處:

factory的內容就是實際代碼的內容;

與Node模塊不一樣之處:

  1. AMD須要define定義一個模塊,Node實現中是隱式包裝的;
  2. 內容須要經過返回的方式實現導出;

7.3CMD規範

CMD規範由國內的玉伯提出,與AMD規範的主要區別在於定義模塊和依賴引入的部分。

AMD須要在聲明模塊的時候指定全部的依賴,經過形參傳遞到模塊內容;

define(['dep1','dep2'],function (dep1,dep2){
    return function (){};
})

7.4兼容多種模塊規範

爲了讓同一個模塊能夠運行在先後端,須要考慮兼容前端也實現了模塊規範的環境。爲了保持先後端的一致性,類庫開發者須要將類庫代碼包裝在一個閉包內。

相關文章
相關標籤/搜索