模塊(Module)和包(Package)是 Node.js 最重要的支柱。開發一個具備必定規模的程序不可能只用一個文件,一般須要把各個功能拆分、封裝,而後組合起來,模塊正是爲了實現這種方式而誕生的。在瀏覽器 Javascript 中,腳本模塊的拆分和組合一般使用 HTML 的 script 標籤來實現。Node.js 提供了 require 函數來調用其它模塊,並且模塊都是基於文件的。
html
Node.js 的模塊和包常常被相提並論,由於模塊和包是沒有本質區別的,兩個概念也時常混用。若是要辨析,那麼能夠把包理解成是實現某個功能模塊的集合,用於發佈和維護。對使用者來講,模塊和包的區別是透明的,所以常常不作區分。
node
一、什麼是模塊
git
模塊是 Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個 Node.js 文件就是一個模塊,這個文件多是 Javascript 代碼、JSON 或者編譯過的 C/C++ 擴展。github
例如 var http = require(' http '),其中,http 是 Node.js 的一個核心模塊,其內部是用 C++ 實現的,外部用 Javascript 封裝。咱們經過 require 函數獲取了這個模塊,而後才能使用其中的對象。
web
二、建立及加載模塊express
2-一、建立模塊npm
在 Node.js 中,建立一個模塊很是簡單,由於一個文件就是一個模塊,例如:
編程
var hello = require('./hello'); hello.world();
上例中,代碼 require(' ./hello ') 引入了當前目錄下的 hello.js 文件(./ 表示當前目錄,Node.js 默認後綴爲 js)
json
Node.js 提供了兩個 exports 和 require 兩個對象,其中,exports 是模塊公開的接口,require 用於從外部獲取一個模塊的接口,即獲取模塊的 exports 對象。數組
經過一個例子簡單的瞭解下模塊,建立一個 module.js 文件,代碼以下:
var name; exports.setName = function( uname ){ name = uname; }; exports.sayHello = function(){ console.log('Hello' +name); };
在同一目錄下建立 getmoudle.js 文件,代碼以下:
//引用當前目錄下的 module.js 模塊 var myModule = require('./module'); myModule.setName('Roger'); myModule.sayHello();
運行 node getmodule.js,控制檯輸出結果爲:
在這個例子中,module.js 經過 exports 對象 setName 和 sayHello 做爲模塊的訪問接口,在 getmodule.js 中經過 require(' ./module ') 加載這個模塊,而後就能夠直接訪問 module.js 中 exports 對象的成員函數了。
這種接口封裝方式比許多語言要簡潔的多,同時也不失優雅,未引入違反語義的特性,符合傳統的編程邏輯。在這個基礎上,咱們能夠構建大型的應用程序,npm 提供的上萬個模塊都是經過這種方式搭建起來的。
2-二、單次加載
上面這個例子有點相似於建立一個對象,但實際上和對象又有本質的區別,由於 require 不會重複加載模塊,也就是說不管調用多少次 require,得到的模塊都是同一個。
新建一個 loadmoudle.js 文件,代碼以下:
var hello1 = require('./module'); hello1.setName('Roger'); var hello2 = require('./module'); hello2.setName('Sarahling'); hello1.sayHello();
運行 node loadmodule.js,控制檯輸出結果爲:
爲何不是「Hello Roger」呢?這是由於變量 hello1 和 hello2 指向的是同一個實例,所以 hello1.setName 的結果被 hello2.setName 覆蓋,最終輸出結果是由後者決定的。
2-三、覆蓋 exports
有時候咱們只是想把一個對象封裝到模塊中,例如,新建一個 singleObject.js 文件,代碼以下:
function Hello(){ var name; this.setName = function(uname){ name = uname; }; this.sayHello = function(){ console.log('Hello '+name); } } module.exports = Hello;
新建一個 getHello.js 文件,得到這個對象:
var Hello = require('./singleObject'); hello = new Hello(); hello.setName('Bob'); hello.sayHello();
運行 node getHello.js,控制檯輸出結果爲:
注意,模塊接口的惟一變化是使用 module.exports = Hello 代替了 exports.Hello = Hello。在外部引用模塊時,其接口對象就是要輸出的 Hello 對象自己,而不是原先的 exports。
事實上,exports 自己僅僅是一個普通的空對象,即{},它專門用來聲明接口,本質上是經過它爲模塊閉包的內部創建一個有限的訪問接口。由於它沒有任何特殊的地方,因此可用其它東西來代替。
注意:不能夠經過對 exports 直接賦值代替對 module.exports 賦值。exports 實際上只是一個和 module.exports 指向同一個對象的變量,它自己會在模塊執行結束後釋放,但 module 不會,所以只能經過指定 module.exports 來改變訪問接口。
三、服務端的模塊放在哪裏
在 Node.js 初識 HTTP 模塊 介紹中,咱們就已經見到了模塊的使用,像這樣
//第一步: 引入 http 模塊 var http = require('http'); //第二步:建立一個服務器(requestListener 是一個函數,裏面有2個參數,一個請求消息,一個響應消息) var server = http.createServer(function(req, res){ }); //第三步:服務器監聽本地的82端口 server.listen(8082, '127.0.0.1');
Node.js 中自帶了一個叫作 http 的模塊,咱們在代碼中請求它並把返回值賦值給一個本地變量,這把咱們的本地變量變成了一個擁有全部 http 模塊所提供的公共方法的對象。
Node.js 的 require 方法中文件查找策略以下:
因爲 Node.js 中存在 4 類模塊(原生模塊和 3種文件模塊),儘管 require 方法及其簡單,可是內部的加載倒是十分複雜的,其加載優先級也各自不一樣
3-一、從文件模塊緩存中加載
儘管原生模塊與文件模塊的優先級不一樣,可是都不會優先於從文件模塊的緩存中加載已經存在的模塊。
3-二、從原生模塊加載
原生模塊的優先級僅次於文件模塊緩存的優先級。require方法在解析文件名以後,優先檢查模塊是否在原生模塊列表中。以http模塊爲例,儘管在目錄下存在一個http/http.js/http.node/http.json文件,require("http")都不會從這些文件中加載,而是從原生模塊中加載。
原生模塊也有一個緩存區,一樣也是優先從緩存區加載。若是緩存區沒有被加載過,則調用原生模塊的加載方式進行加載和執行。
3-三、從文件加載
當文件模塊緩存中不存在,並且不是原生模塊的時候,Node.js會解析require方法傳入的參數,並從文件系統中加載實際的文件,加載過程當中的包裝和編譯細節在前一節中已經介紹過,這裏咱們將詳細描述查找文件模塊的過程,其中,也有一些細節值得知曉。
require方法接受如下幾種參數的傳遞:
http、fs、path等,原生模塊。
./mod或../mod,相對路徑的文件模塊。
/pathtomodule/mod,絕對路徑的文件模塊。
mod,非原生模塊的文件模塊。
四、建立包
包是在模塊基礎上更深一步的抽象,Node.js 的包相似於 C/C++ 的函數庫或者 Java/.Net的類庫。它將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和版本控制。Node.js 根據 CommonJS 規範實現了包機制,開發了 npm來解決包的發佈和獲取需求。
Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明文件 package.json。嚴格符合CommonJS 規範的包應該具有如下特徵:
package.json 必須在包的頂層目錄下;
二進制文件應該在 bin 目錄下;
JavaScript 代碼應該在 lib 目錄下;
文檔應該在 doc 目錄下;
單元測試應該在 test 目錄下。
Node.js 對包的要求並無這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範便可。固然爲了提升兼容性,咱們仍是建議你在製做包的時候,嚴格遵照 CommonJS 規範。
4-一、做爲文件夾的模塊
模塊與文件是一一對應的。文件不只能夠是 JavaScript 代碼或二進制代碼,還能夠是一個文件夾。最簡單的包,就是一個做爲文件夾的模塊。下面咱們來看一個例子,創建一個叫作 somepackage 的文件夾,在其中建立 index.js,內容以下:
exports.hello = function(){ console.log('Hello World!'); }
而後在 somepackage 以外創建 getpackage.js,內容以下:
var somePackage = require('./somepackage'); somePackage.hello();
運行 getpackage.js,控制輸出結果以下:
咱們使用這種方法能夠把文件夾封裝爲一個模塊,即所謂的包。包一般是一些模塊的集合,在模塊的基礎上提供了更高層的抽象,至關於提供了一些固定接口的函數庫。經過定製package.json,咱們能夠建立更復雜、更完善、更符合規範的包用於發佈。
4-二、package.json
在 somepackage 文件夾下,建立一個叫作 package.json 的文件,內容以下所示:
{ "main" : "./lib/interface.js" }
而後將 index.js 重命名爲 interface.js 並放入 lib 子文件夾下。以一樣的方式再次調用這個包,依然能夠正常使用。
Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段,將其做爲包的接口模塊,若是 package.json 或 main 字段不存在,會嘗試尋找 index.js 或 index.node 做爲包的接口。
package.json 是 CommonJS 規定的用來描述包的文件,徹底符合規範的 package.json 文件應該含有如下字段。
name:包的名稱,必須是惟一的,由小寫英文字母、數字和下劃線組成,不能包含空格。
description:包的簡要說明。
version:符合語義化版本識別規範的版本字符串。
keywords:關鍵字數組,一般用於搜索。
maintainers:維護者數組,每一個元素要包含 name、email (可選)、web (可選)字段。
contributors:貢獻者數組,格式與maintainers相同。包的做者應該是貢獻者數組的第一個元素。
bugs:提交bug的地址,能夠是網址或者電子郵件地址。
licenses:許可證數組,每一個元素要包含 type (許可證的名稱)和 url (連接到許可證文本的地址)字段。
repositories:倉庫託管地址數組,每一個元素要包含 type (倉庫的類型,如 git )、url (倉庫的地址)和 path (相對於倉庫的路徑,可選)字段。
dependencies:包的依賴,一個關聯數組,由包名稱和版本號組成。
下面是一個徹底符合 CommonJS 規範的 package.json 示例:
{ "name": "mypackage", "description": "Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package.", "version": "0.7.0", "keywords": [ "package", "example" ], "maintainers": [ { "name": "Bill Smith", "email": "bills@example.com", } ], "contributors": [ { "name": "BYVoid", "web": "http://www.byvoid.com/" } ], "bugs": { "mail": "dev@example.com", "web": "http://www.example.com/bugs" }, "licenses": [ { "type": "GPLv2", "url": "http://www.example.org/licenses/gpl.html" } ], "repositories": [ { "type": "git", "url": "http://github.com/BYVoid/mypackage.git" } ], "dependencies": { "webkit": "1.2", "ssl": { "gnutls": ["1.0", "2.0"], "openssl": "0.9.8" } } }
4-三、Node.js 包管理器
Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已經成了 Node.js 包的標準發佈平臺,用於 Node.js 包的發佈、傳播、依賴控制。npm 提供了命令行工具,使你能夠方便地下載、安裝、升級、刪除包,也可讓你做爲開發者發佈並維護包。
(1)、獲取一個包
使用 npm 安裝包的命令格式爲:
npm [install/i] [package_name]
好比你要安裝 express,能夠在命令行運行:
$ npm install express
或
$ npm i express
能夠看到安裝信息以下:
此時 express 就安裝成功了,而且放置在當前目錄的 node_moudles 子目錄下。npm 在獲取 express 的時候還將自動解析其依賴,並獲取 express 依賴的 mime、mkdirp、qs 和 connect。
(2)、本地模式 和 全局模式
npm 在默認狀況下會從 https://www.npmjs.com/ 搜索或下載包,將包安裝到當前目錄的 node_moudles 子目錄下。
在使用 npm 安裝包的時候,有兩種模式:本地模式和全局模式。默認狀況下咱們使用 npm install 命令就是採用本地模式,即把包安裝到當前目錄的 node_modules 子目錄下。Node.js 的 require 在加載模塊時會嘗試搜尋 node_modules 子目錄,所以使用 npm 本地模式安裝的包能夠直接被引用。
npm 還有另外一種不一樣的安裝模式被成爲全局模式,使用方法爲:
npm [install/i] -g [package_name]
與本地模式的不一樣之處就在於多了一個參數 -g。
爲何要使用全局模式呢?多數時候並非由於許多程序都有可能用到它,爲了減小多重副本而使用全局模式,而是由於本地模式不會註冊 PATH 環境變量。舉例說明,咱們安裝supervisor 是爲了在命令行中運行它,譬如直接運行 supervisor script.js,這時就須要在 PATH環境變量中註冊 supervisor。npm 本地模式僅僅是把包安裝到 node_modules 子目錄下,其中 的 bin 目錄沒有包含在 PATH 環境變量中,不能直接在命令行中調用。而當咱們使用全局模式安裝時,npm 會將包安裝到系統目錄,譬如 /usr/local/lib/node_modules/,同時 package.json 文件中 bin 字段包含的文件會被連接到 /usr/local/bin/。/usr/local/bin/ 是在PATH 環境變量中默認定義的,所以就能夠直接在命令行中運行 supervisor script.js命令了。
本地模式和全局模式的特色以下表所示:
模式 | 可經過 require 使用 |
註冊PATH |
本地模式 | 是 | 否 |
全局模式 |
否 | 是 |
總而言之,當咱們要把某個包做爲工程運行時的一部分時,經過本地模式獲取,若是要 在命令行下使用,則使用全局模式安裝。
(3)、建立全局連接
npm 提供了一個有趣的命令 npm link(不支持 Windows),它的功能是在本地包和全局包之間建立符號連接。咱們說過使用全局模式安裝的包不能直接經過 require 使用,但經過 npm link命令能夠打破這一限制。舉個例子,咱們已經經過 npm install -g express 安裝了 express,這時在工程的目錄下運行命令:
$ npm link express ./node_modules/express -> /usr/local/lib/node_modules/express
咱們能夠在 node_modules 子目錄中發現一個指向安裝到全局的包的符號連接。經過這 種方法,咱們就能夠把全局包當本地包來使用了。
除了將全局的包連接到本地之外,使用 npm link命令還能夠將本地的包連接到全局。使用方法是在包目錄( package.json 所在目錄)中運行 npm link 命令。若是咱們要開發一個包,利用這種方法能夠很是方便地在不一樣的工程間進行測試。
(4)、包的發佈
npm 能夠很是方便地發佈一個包,比 pip、gem、pear 要簡單得多。在發佈以前,首先須要讓咱們的包符合 npm 的規範,npm 有一套以 CommonJS 爲基礎包規範,但與 CommonJS並不徹底一致,其主要差異在於必填字段的不一樣。經過使用 npm init 能夠根據交互式問答產生一個符合標準的 package.json,例如建立一個名爲 byvoidmodule 的目錄,而後在這個目錄中運行npm init:
這樣就在 byvoidmodule 目錄中生成一個符合 npm 規範的 package.json 文件。建立一個index.js 做爲包的接口,一個簡單的包就製做完成了。
在發佈前,咱們還須要得到一個帳號用於從此維護本身的包,使用 npm adduser 根據提示輸入用戶名、密碼、郵箱,等待帳號建立完成。完成後可使用 npm whoami 測驗是否已經取得了帳號。
接下來,在 package.json 所在目錄下運行 npm publish,稍等片刻就能夠完成發佈了。
打開瀏覽器,訪問 http://search.npmjs.org/ 就能夠找到本身剛剛發佈的包了。如今咱們能夠在世界的任意一臺計算機上使用 npm install byvoidmodule 命令來安裝它。下圖是npmjs.org上包的描述頁面。
若是你的包未來有更新,只須要在 package.json 文件中修改 version 字段,而後從新使用 npm publish 命令就好了。若是你對已發佈的包不滿意(好比咱們發佈的這個毫無心義的包),可使用 npm unpublish 命令來取消發佈。