[Node]核心API

1. Events


1.1 EventEmitter

因爲在瀏覽器中Event模型是綁定在DOM上的,所以Node創建了EventEmitter類來提供基礎的事件功能。所有Node的事件功能圍繞着EventEmitter,它通常不會直接調用。

EventEmitter類提供了一系列方法,最主要的兩個是on和emit。on方法爲事件創建了監聽器,例如:

server.on('event', function(a, b, c) {
});

on方法接受兩個參數:需要監聽的事件的名稱和當事件觸發時需要調用的函數。因爲EventEmitter是接口,從EventEmitter繼承的類需要使用new關鍵字來構造,例如:

var utils = require('utils');
var EventEmitter = require('events').EventEmitter;
var Server = function() {
  console.log('init');
};
utils.inherits(Server, EventEmitter);
var s = new Server();
s.on('abc', function() {
  console.log('abc');
});

utils模塊的inherits方法能夠把EventEmitter類的方法添加到創建的Server類中,即所有Server的新實例都能夠使用EventEmitter的方法。可以通過如下代碼來觸發這個事件:

s.emit('abc');

這些事件是針對某個實例的,不存在全局的事件,當調用on方法的時候,需要綁定在特定的基於EventEmitter的對象上。Server類不同的實例之間也不會共享事件。


1.2 Callback

使用事件的一個重要部分就是處理回調函數。當調用emit時,除了事件的名稱,可以傳入任意數目的參數,例如:

s.emit('abc', a, b, c);

當emit調用包含變量時,如下代碼會被用來調用對應的事件監聽器:

if (argument.length <= 3) { 
  // quick
  handler.call(this, arguments[1], arguments[2]);
} else { 
  // slow
  var args = Array.prototype.slice.call(arguments, 1);
  handler.apply(this, args);
}

如果傳給emit()的參數只有3個或更少,該方法就會使用捷徑,直接調用call方法。否則,它就會使用較慢的apply方法,以數組的方式傳遞所有的參數。


2. HTTP


2.1 HTTP服務器

HTTP服務器是最常用的使用情景,它還有很多的功能。HTTP模塊的服務器部分提供了構建複雜、全面的Web服務器的原始工具。

使用HTTP服務器的第一步就是調用http.createServer()方法來創建一個新的服務器,這會返回一個新的Server類的實例,裏面只包含少數的方法,更多的功能則使用事件來提供,例如:

var http = require('http'); 
http.createServer(function(req, res) { 
  res.writeHead(200, {}); 
  res.end('Hello World'); 
}).listen(9000);

首先,我們包含一個http模塊。而後調用createServer並傳入一個函數作爲參數,讓它綁定在request事件上。最後,我們讓創建的服務器監聽9000端口。

http服務器支持幾種事件,都和客戶端TCP或者HTTP連接相關聯。

connection和close事件表示了與客戶端的TCP連接的建立與關閉,如果客戶端使用的是HTTP 1.1協議,它是支持keepalive的,這意味着這些TCP連接可能會跨越多個HTTP請求。

request、checkContinue、upgrade和clientError事件則關聯在HTTP請求上。chekContinue是一個特殊的事件,當客戶端以數據流的方式將數據發送給服務器時,可以對HTTP請求進行更直接的控制。upgrade事件在一個客戶端請求協議升級時會觸發,除非綁定了此事件的事件處理器,否則http服務器拒絕HTTP升級請求。clientError事件會把客戶端發送的error事件傳遞出來。

當爲請求創建一個新的TCP流時,就會觸發connection事件,這個事件會把TCP流作爲參數傳給該請求。該數據流也可以在request使用的時候,通過request、connection變量獲得。但每個流只會觸發connection事件一次,所以可能會出現從一個客戶端來的多個請求只對應一次connection事件。


2.2 HTTP客戶端

如果想向遠程服務器發起HTTP連接,Node也是很好的選擇,Node在許多情景下都適用使用,比如使用Web service,連接到文檔數據庫,或是抓取網頁。可以使用同樣的http模塊來發起HTTP請求,但應該使用http.ClientRequest類,例如:

var http = require('http'); 
var opts = { 
  host: 'www.google.com', 
  port: 80, 
  path: '/', 
  method: 'get'
}; 
var req = http.request(opts, function(res) { 
  console.log(res); 
  res.on('data', function(data) { 
    console.log(data); 
  }); 
});
req.end();

首先是配置對象,它定義了請求的許多功能,我們必須提供名字(host),端口(port)和路徑(path),方法(method)是可選項,如果沒有指定,默認會設置爲GET。其次,要用配置對象來創建一個http.ClientRequest實例,並傳入options對象和回調函數,傳入的回調函數會監聽response事件,並在接收到response事件時,處理request的數據。

最後需要注意的是,需要結束(end)該請求,因爲這是一個GET請求,所以不會往服務器發送任何數據,但對於其他的HTTP方法,比如POST,可能需要發送數據,request會等待end()方法調用後,才初始化HTTP請求。

GET是常見的HTTP請求方式,因此提供了一個專門的工廠方法來更方便地使用,例如:

var http = require('http');
var opts = {
  host: 'www.test.com',
  port: 80,
  path: '/'
};
var req = http.get(opts, function(res) {
  console.log(res);
  res.on('data', function(data) {
    console.log(data);
  });
});

和GET請求的功能一樣,但POST還需要往上發送一些數據,例如:

var http = require('http'); 
var opts = { 
  host: 'www.test.com', 
  port: 80, 
  path: '/'
}; 
var req = http.request(opts, function(res) { 
  console.log(res); 
  res.on('data', function(data) { 
    console.log(data); 
  }); 
}); 
req.write('my data');
req.write('more of my data');
req.end();

ClientResponse對象保存了關於請求的許多信息,它的一些顯著的屬性也很有用,包括statusCode(HTTP狀態)和header屬性(響應頭對象)。


2.3 URL

URL模塊提供瞭解析和處理URL字符串的便利工具,該模塊提供了三個方法:parse、format和resolve。這裏先演示parse的使用,例如:

var URL = require('url');
var parsedUrl = URL.parse("http://www.test.com/?param=1");

它返回代表URL各個部分的數據結構,其中包括href、protocol、host、auth、hostname、port、pathname、search、query、hash。

parse可以有兩個參數:url字符串,及一個可選的布爾值,用來確定queryString是否該用querystring模塊來解析。如果第二個參數是false(默認值),query將包含一個與search類似的字符串,但去掉了開頭的"?"。


2.4 querystring

querystring模塊是用來處理query字符串的簡單輔助模塊,它提供了從query字符串中輕鬆提取對象的方法,主要功能有parse和decode,還包括一些內部輔助函數,如escape、unescape、unescapeBuffer、encode和stringify。如果有一個query字符符,可以使用parse來使它變成一個對象,例如:

var qs = require('querystring');
qs.parse('a=1&b=2&c=3');

生成的對象中的屬性是對應query字符串中的關鍵字和變量值。還需要注意的是:數字是返回成字符串的,並非數字型,並且傳入的query字符串不能包含URL中標記的"?"。

querystring的另一個重要部分是encode。該函數把輸入的key-value格式的對象轉換成query字符串的格式,如果需要使用HTTP請求,這會非常方便。


3. I/O


3.1 數據流

Node中的許多組件提供了連續輸出或可連續處理輸入的功能,stream API提供了常用的方法以及數據流具體實現時需要使用的屬性。數據流分爲可讀、可寫和可讀寫。

可讀的數據流API是一組方法和事件,提供了數據源在發送時訪問數據塊的功能。基本上可讀數據流是與觸發data事件相關的,這些事件流代表了數據的流形式,例如:

var fs = require('fs');
var filehandel = fs.readFile('test.txt', function(err, data) {
  console.log(data);
});

有時需要等待完整的數據都可用後再進行操作,就會用到數據池模式,例如:

var spool = '';
stream.on('data', function(data) {
  spool += data;
});
stream.on('end', function() {
  console.log(spool);
});


3.2 文件系統

文件系統模塊爲它所有的功能都提供了異步和同步的方法,但是強烈建議用異步的方法,除非是用Node來創建命令行腳本。在處理異步時遇到的主要問題是執行次序具有不確定性,例如:

var fs = require('fs');
fs.readFile('test.txt', function(e, data) {
  console.log('Data: ' + data);
});
fs.unlink('test.txt');

這段代碼表面上看沒問題,但運行時有時正常,有時卻不正常。因此需要一種模式,來指定想要運行的次序,比如回調函數嵌套,例如:

var fs = require('fs');
fs.readFile('test.txt', function(e, data) {
  console.log('Data: ' + data);
  fs.unlink('test.txt');
});


3.3 Buffer

雖然Node也使用JavaScript,但是它並沒有二進制數據的原生表現形式。爲此Node帶來了Buffer類,爲操作二進制數據彌補了短板。

Buffer是V8引擎上的擴展,實際上是對內存的直接分配。創建一個Buffer後,它的大小就固定了,如果需要添加更多的數據,就必須把老Buffer複製到一個更大的Buffer中。

一旦把內容複製到一個Buffer後, 它就會以二進制形式存儲起來,當然也可能隨時把Buffer中的二進制內容轉換成其他的格式,比如字符串。所以Buffer只由它的大小來定義,而非通過編碼或者其他任何指示含義的方式。

創建Buffer可以使用3種參數:指定Buffer的字節長度,需要拷貝到Buffer裏的字節數組,或是需要拷貝到Buffer裏的字符串,例如:

new Buffer(10); 
new Buffer('test');
new Buffer('test', 'utf8');

Buffer是從內存直接分配的,它並不會對原有的內容進行初始化,這與原生的JavaScript類型不同。Node提供了一些操作來簡化字符串和Buffer的操作。

首先,不需要在創建Buffer前提前計算字符串的長度,只要把字符串作爲參數傳給創建Buffer的函數。或者也可以使用Buffer.byteLength()方法來獲得字符串在編碼上的字節長度。

其次還可以往已經存在的Buffer上寫入字符串。Buffer.write()會把字符串寫到Buffer指定的位置上。如果從Buffer指定位置開始有足夠空間,整個字符串都會被寫入,否則字符串的尾部會被截斷。Buffer.write()會返回一個數字,表示有多少字節被成功寫入。如果條件允許,當寫入UTF-8時,Buffer.write()寫入的字符串會以一個NULL字符結尾。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章