Node.js快速入門
1 異步式I/O 與事件式編程
Node.js 最大的特點就是異步式I/O (或者非阻塞 I/O )與事件緊密結合的編程模式。這種模式與傳統的同步式 I/O 線性的編程思路有很大的不同,因為控制流很大程度上要靠事件和回調函數來組織,一個邏輯要拆分為若干個單元。
阻塞模式下,一個線程只能處理一項任務,要想提高吞吐量必須通過多線程。而非阻塞
模式下,一個線程永遠在執行計算操作,這個線程所使用的 CPU 核心利用率永遠是 100%,
I/O 以事件的方式通知。在阻塞模式下,多線程往往能提高系統吞吐量,因為一個線程阻塞時還有其他線程在工作,多線程可以讓 CPU 資源不被阻塞中的線程浪費。而在非阻塞模式下,線程不會被 I/O 阻塞,永遠在利用 CPU。多線程帶來的好處僅僅是在多核 CPU 的情況下利用更多的核,而Node.js的單線程也能帶來同樣的好處。這就是為什么 Node.js 使用了單線程、非阻塞的事件編程模式。
單線程事件驅動的異步式 I/O 比傳統的多線程阻塞式 I/O 究竟好在哪里呢?簡而言之,異步式I/O 就是少了多線程的開銷。對操作系統來說,創建一個線程的代價是十分昂貴的,需要給它分配內存、列入調度,同時在線程切換的時候還要執行內存換頁,CPU 的緩存被清空,切換回來的時候還要重新從內存中讀取信息,破壞了數據的局部性。
讓我們看看在 Node.js 中如何用異步的方式讀取一個文件:
var http = require("http"); http.createServer(function (req, res) { res.writeHead(200, { "Content-Type": "text/html" }); res.write('<h1>Node.js</h1>'); res.end("Hello World \n"); }).listen(8080, "127.0.0.1"); console.log("Server running at http://127.0.0.1:8080/");
運行的結果如下:
end.
Contents of thefile.

Node.js 也提供了同步讀取文件的API :
//readfilesync.js var fs = require('fs'); var data = fs.readFileSync('file.txt', 'utf-8'); console.log(data); console.log('end.');
運行的結果與前面不同,如下所示:
$ nodereadfilesync.js
Contents of thefile.
end.
同步式讀取文件的方式比較容易理解,將文件名作為參數傳入 fs.readFileSync 函
數,阻塞等待讀取完成后,將文件的內容作為函數的返回值賦給 data 變量,接下來控制臺
輸出 data 的值,最后輸出 end. 。
異步式讀取文件就稍微有些違反直覺了,end.先被輸出。要想理解結果,我們必須先
知道在 Node.js 中,異步式I/O 是通過回調函數來實現的。fs.readFile 接收了三個參數,
第一個是文件名,第二個是編碼方式,第三個是一個函數,我們稱這個函數為回調函數。
JavaScript 支持匿名的函數定義方式,譬如我們例子中回調函數的定義就是嵌套在fs.readFile 的參數表中的。這種定義方式在 JavaScript 程序中極為普遍,與下面這種定義方式實現的功能是一致的:
/readfilecallback.js function readFileCallBack(err, data) { if (err) { console.error(err); } else { console.log(data); } } var fs = require('fs'); fs.readFile('file.txt', 'utf-8', readFileCallBack); console.log('end.');
fs.readFile 調用時所做的工作只是將異步式I/O 請求發送給了操作系統,然后立即返回并執行后面的語句,執行完以后進入事件循環監聽事件。當 fs 接收到 I/O 請求完成的事件時,事件循環會主動調用回調函數以完成后續工作。因此我們會先看到 end. ,再看到file.txt 文件的內容。
2事件
Node.js 所有的異步 I/O 操作在完成時都會發送一個事件到事件隊列。在開發者看來,事
件由 EventEmitter 對象提供。前面提到的 fs.readFile 和 http.createServer 的回調函數都是通過 EventEmitter 來實現的。下面我們用一個簡單的例子說明 EventEmitter 的用法:
//event.js var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function () { console.log('some_event occured.'); }); setTimeout( function () { event.emit('some_event'); }, 1000);
運行這段代碼,1秒后控制臺輸出了 some_event occured. 。

其原理是 event 對象注冊了事件 some_event 的一個監聽器,然后我們通過 setTimeout 在1000 毫秒以后向 event 對象發送事件 some_event ,此時會調用some_event 的監聽器。
Node.js 的事件循環機制
Node.js 在什么時候會進入事件循環呢?答案是Node.js 程序由事件循環開始,到事件循
環結束,所有的邏輯都是事件的回調函數,所以 Node.js 始終在事件循環中,程序入口就是事件循環第一個事件的回調函數。事件的回調函數在執行的過程中,可能會發出 I/O 請求或直接發射(emit )事件,執行完畢后再返回事件循環,事件循環會檢查事件隊列中有沒有未處理的事件,直到程序結束,下圖說明了事件循環的原理。
3、模塊和包
模塊(Module)和包(Package)是 Node.js 最重要的支柱。開發一個具有一定規模的程序不可能只用一個文件,通常需要把各個功能拆分、封裝,然后組合起來,模塊正是為了實現這種方式而誕生的。在瀏覽器 JavaScript 中,腳本模塊的拆分和組合通常使用 HTML 的 script 標簽來實現。Node.js提供了 require 函數來調用其他模塊,而且模塊都是基于
文件的,機制十分簡單。
Node.js 的模塊和包機制的實現參照了CommonJS 的標準,但并未完全遵循。不過
兩者的區別并不大,一般來說你大可不必擔心,只有當你試圖制作一個除了支持 Node.js
之外還要支持其他平臺的模塊或包的時候才需要仔細研究。通常,兩者沒有直接沖突的
地方。
我們經常把 Node.js 的模塊和包相提并論,因為模塊和包是沒有本質區別的,兩個概念也時常混用。如果要辨析,那么可以把包理解成是實現了某個功能模塊的集合,用于發布
和維護。對使用者來說,模塊和包的區別是透明的,因此經常不作區分。
3.1、什么是模塊
模塊是Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個
Node.js 文件就是一個模塊,這個文件可能是JavaScript 代碼、JSON 或者編譯過的 C/C++ 擴展。
3.2 創建及加載模塊
介紹了什么是模塊之后,下面我們來看看如何創建并加載它們。在 Node.js 中,創建一個模塊非常簡單,因為一個文件就是一個模塊,我們要關注的問題僅僅在于如何在其他文件中獲取這個模塊。Node.js 提供了 exports 和 require 兩個對象,其中 exports 是模塊公開的接口,require 用于從外部獲取一個模塊的接口,即所獲取模塊的 exports 對象。
讓我們以一個例子來了解模塊。創建一個 module.js 的文件,內容是://module.js var name; exports.setName = function (thyName) { name = thyName; }; exports.sayHello = function () { console.log('Hello ' + name); };在同一目錄下創建 getmodule.js ,內容是:
//getmodule.js var myModule = require('./module'); myModule.setName('BYVoid'); myModule.sayHello();
運行node getmodule.js,結果是:
Hello BYVoid

在以上示例中,module.js 通過 exports 對象把 setName 和 sayHello 作為模塊的訪問接口,在getmodule.js 中通過require('./module') 加載這個模塊,然后就可以直接訪問 module.js 中 exports 對象的成員函數了。
3.4、 創建包
包是在模塊基礎上更深一步的抽象,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 規范。
3.4.1. 作為文件夾的模塊
模塊與文件是一一對應的。文件不僅可以是JavaScript 代碼或二進制代碼,還可以是一個文件夾。最簡單的包,就是一個作為文件夾的模塊。下面我們來看一個例子,建立一個叫做package 的文件夾,在其中創建index.js ,內容如下:
//package/index.js exports.hello = function () { console.log('Hello world.'); };然后在 package 之外建立 getpackage.js ,內容如下:
//getpackage.js var package = require('./ package'); package.hello();
運行 node getpackage.js,控制臺將輸出結果 Hello world.。

我們使用這種方法可以把文件夾封裝為一個模塊,即所謂的包。包通常是一些模塊的集
合,在模塊的基礎上提供了更高層的抽象,相當于提供了一些固定接口的函數庫。通過定制
package.json,我們可以創建更復雜、更完善、更符合規范的包用于發布。
3.4.2. package.json
在前面例子中的package 文件夾下,我們創建一個叫做package.json 的文件,內容如
下所示:
{
"main" :"./lib/interface.js"
}
然后將 index.js 重命名為interface.js 并放入 lib 子文件夾下。以同樣的方式再次調用這個包,依然可以正常使用。
Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段,將其作為包的接口模塊,如果 package.json 或 main 字段不存在,會嘗試尋找index.js 或 index.node 作為包的接口。
3.5 Node.js 包管理器
Node.js包管理器,即npm 是Node.js 官方提供的包管理工具,它已經成了Node.js 包的標準發布平臺,用于Node.js 包的發布、傳播、依賴控制。npm 提供了命令行工具,使你可以方便地下載、安裝、升級、刪除包,也可以讓你作為開發者發布并維護包。
3.5.1. 獲取一個包
使用 npm 安裝包的命令格式為:
npm [install/i][package_name]
例如你要安裝 express ,可以在命令行運行:
$ npm install express
或者:
$ npm i express
隨后你會看到以下安裝信息:
npm http GEThttps://registry.npmjs.org/express
npm http 304https://registry.npmjs.org/express
npm http GEThttps://registry.npmjs.org/mime/1.2.4
npm http GEThttps://registry.npmjs.org/mkdirp/0.3.0
npm http GEThttps://registry.npmjs.org/qs
npm http GEThttps://registry.npmjs.org/connect
npm http 200https://registry.npmjs.org/mime/1.2.4
npm http 200https://registry.npmjs.org/mkdirp/0.3.0
npm http 200https://registry.npmjs.org/qs
npm http GEThttps://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http GEThttps://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200https://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http 200https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200https://registry.npmjs.org/connect
npm http GEThttps://registry.npmjs.org/formidable
npm http 200https://registry.npmjs.org/formidable
express@2.5.8./node_modules/express
-- mime@1.2.4
-- mkdirp@0.3.0
-- qs@0.4.2
-- connect@1.8.5
此時 express 就安裝成功了,并且放置在當前目錄的 node_modules 子目錄下。npm 在npm 之于 Node.js ,就像 pip 之于 Python,gem 之于 Ruby ,pear 之于 PHP,CPAN 之于 Perl ……同時也像apt-get 之于 Debian/Ubutnu ,yum 之于 Fedora/RHEL/CentOS ,homebrew 之于 Mac OS X 。獲取 express 的時候還將自動解析其依賴,并獲取 express 依賴的 mime、mkdirp、qs 和 connect。
3.5.2. 本地模式和全局模式
npm 在默認情況下會從http://npmjs.org搜索或下載包,將包安裝到當前目錄的node_modules子目錄下。
在使用 npm 安裝包的時候,有兩種模式:本地模式和全局模式。默認情況下我們使用 npm install命令就是采用本地模式,即把包安裝到當前目錄的 node_modules 子目錄下。Node.js 的 require 在加載模塊時會嘗試搜尋 node_modules 子目錄,因此使用 npm 本地模式安裝的包可以直接被引用。
npm 還有另一種不同的安裝模式被成為全局模式,使用方法為: npm [install/i] -g [package_name] 與本地模式的不同之處就在于多了一個參數-g。我們在 介紹 supervisor 那個小節中使用了 npm install -gsupervisor 命令,就是以全局模式安裝supervisor 。
3.5.3包的發布
npm 可以非常方便地發布一個包,比 pip 、gem 、pear 要簡單得多。在發布之前,首先需要讓我們的包符合 npm 的規范,npm 有一套以 CommonJS 為基礎包規范,但與 CommonJS 并不完全一致,其主要差別在于必填字段的不同。通過使用 npm init 可以根據交互式問答產生一個符合標準的package.json,例如創建一個名為 module 的目錄,然后在這個目錄中運行npm init :
$ npm init;根據提示完成輸入后就可以了;
這樣就在module 目錄中生成一個符合npm 規范的 package.json 文件。創建一個 index.js 作為包的接口,一個簡單的包就制作完成了。
在發布前,我們還需要獲得一個賬號用于今后維護自己的包,使用 npm adduser 根據提示輸入用戶名、密碼、郵箱,等待賬號創建完成。完成后可以使用 npm whoami 測驗是否已經取得了賬號。 接下來,在package.json 所在目錄下運行 npmpublish ,稍等片刻就可以完成發布了。打開瀏覽器,訪問 http://search.npmjs.org/ 就可以找到自己剛剛發布的包了。現在我們可以在世界的任意一臺計算機上使用 npm install byvoidmodule 命令來安裝它。 如果你的包將來有更新,只需要在 package.json 文件中修改 version 字段,然后重新使用 npm publish 命令就行了。如果你對已發布的包不滿意(比如我們發布的這個毫無意義的包),可以使用 npm unpublish 命令來取消發布。
來自: http://blog.csdn.net//u011067360/article/details/17710071