Node.js快速入門

jopen 8年前發布 | 22K 次閱讀 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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!