本學習筆記是根據《Node.js開發指南》一書進行學習。
全局對象
JavaScript中有一個特殊的對象,稱為全局對象(Global Object),它及其所有屬性都可以在程序的任何地方訪問,即全局變量。在瀏覽器JavaScript中,通常window是全局對象,而NodeJs中的全局對象是global,所有全局變量(除了global本身以外)都是global對象的屬性。
全局對象和全局變量
按照ECMAScript的定義,滿足以下條件的變量是全局變量:
- 在最外層定義的變量
- 全局對象的屬性
- 隱式定義的變量(未定義直接賦值的變量)
在NodeJs中你不可能在最外層定義變量,因為所有用戶代碼都是屬于當前模塊的,而模塊本身不是最外層上下文。
永遠使用var定義變量以避免引入全局變量,因為全局變量會污染命名空間,提高代碼的耦合風險。
process
process是一個全局變量,即global對象的屬性。它用于描述當前NodeJs進程狀態的對象,提供了一個與操作系統的簡單接口。
-
process.argv
是命令行參數數組,第一個元素是node,第二個元素是腳本文件名,從第三個元素開始每個元素是一個運行參數。 -
process.stdout
是標準輸出流,通常我們使用的console.log()向標準輸出打印字符,而process.stdout.write()
函數提供了更底層的接口。
-process.stdin
是標準輸入流,初始時它是被暫停的,要想從標準輸入讀取數據,你必須恢復流,并手動編寫流的事件響應函數。 -
process.nextTick(callback)
的功能是為事件循環設置一項任務,NodeJs會在下次事件循環調響應時調用callback
。
// debug.js
console.log(process.argv);
process.stdin.resume();
process.stdin.on("data", function (data) {
process.stdout.write(data.toString());
});
結果:
不要使用setTimeout(fn, 0)
代替process.nextTick(callback)
,前者比后者效率要低得多。
console
console
對象用于向標準輸出流(stdout)或標準錯誤流(stderr)輸出字符。
- console.log()向標準輸出流打印字符并以換行符結束。
- console.error():與console.log()用法相同,只是向標準錯誤流輸出。
- console.trace():向標準錯誤流輸出當前的調用棧。
console.log接受若干個參數,如果只有一個參數,則輸出這個參數的字符串形式。如果有多個參數,則以類似于C語言printf()命令的格式輸出。第一個參數是一個字符串,如果沒有參數,只打印一個換行。
常用工具util
util
是一個Node.js核心模塊,提供常用函數的集合
util.inherits
JavaScript的面向對象特性是基于原型的,與常見的基于類的不同。JavaScript沒有提供對象繼承的語言級別特性,而是通過原型復制來實現的
util.inherits(constructor, superConstructor)
是一個實現對象間原型繼承的函數。
// inherits.js
var util = require("util");
function Base() {
this.name = "base";
this.base = 1991;
this.sayHello = function () {
console.log("Hello"+this.name);
};
}
Base.prototype.showName = function () {
console.log(this.name);
};
function Sub() {
this.name = "sub";
}
util.inherits(Sub, Base);
var newBase = new Base();
newBase.showName();
newBase.sayHello();
console.log(newBase);
var newSub = new Sub();
newSub.showName();
// newSub.sayHello();
console.log(newSub);
Sub
僅僅繼承了Base
在原型中定義的函數,而構造函數內部創造的base
屬性和sayHello
函數都沒有被Sub繼承。同時,在原型中定義的屬性不會被console.log作為對象的屬性輸出。
util.inspect
util.inspect(object,[showHidden],[depth],[colors])
是一個將任意對象轉換為字符串的方法,通常用于調試和錯誤輸出。它至少接受一個參數object,即要轉換的對象。
util
還提供了util.isArray()
、util.isRegExp()
、util.isDate()
、util.isError()
四個類型測試工具,以及util.format()
、util.debug()
等工具。
事件驅動events
events是NodeJs最重要的模塊。NodeJs本身架構就是事件式的,而它提供了唯一的接口,所以堪稱NodeJs事件編程的基石。
事件發射器
events
模塊只提供了一個對象:events.EventEmitter
。EventEmitter
的核心就是事件發射與事件監聽器功能的封裝。EventEmitter
的每個事件由一個事件名和若干個參數組成,事件名是一個字符串,通常表達一定的語義。對于每個事件,EventEmitter
支持若干個事件監聽器。當事件發射時,注冊到這個事件的事件監聽器被依次調用,事件參數作為回調函數參數傳遞。
var EventEmitter = require("events").EventEmitter;
var event = new EventEmitter();
event.on("some_event", function(){
console.log("some_event start1");
});
event.on("some_event", function () {
console.log("some_event start2")
});
event.emit("some_event");
執行以上代碼輸出:
some_event start1
some_event start2
運行結果中可以看到兩個事件監聽器回調函數被先后調用。
EventEmitter
常用的API:
-
EventEmitter.on(event, listener)
為指定事件注冊一個監聽器,接受一個字符串event
和一個回調函數listener
。 -
EventEmitter.emit(event, [arg1], [arg2], [...])
發射event
事件,傳遞若干可選參數到事件監聽器的參數表。 -
EventEmitter.once(event, listener)
為指定事件注冊一個單次監聽器,即監聽器最多只會觸發一次,觸發后立刻解除該監聽 -
EventEmitter.removeListener(event, listener)
移除指定事件的某個監聽器,listener
必須是該事件已經注冊過的監聽器。 -
EventEmitter.removeAllListeners([event])
移除所有事件的所有監聽器,如果指定event
,則移除指定事件的所有監聽器。
var EventEmitter = require("events").EventEmitter;
var event = new EventEmitter();
event.on("some_event", function(){
console.log("some_event start1");
});
event.on("some_event", function () {
console.log("some_event start2")
});
event.emit("some_event");
setTimeout(function(){
event.emit("some_event");
}, 1000);
event.removeAllListeners("some_event");
event.emit("some_event");
執行以上代碼,輸出:
error事件
EventEmitter
定義了一個特殊的事件error
,它包含了“錯誤”的語義,我們在遇到異常的時候通常會發射error
事件。
繼承EventEmitter
大多數時候我們不會直接使用EventEmitter,而是在對象中繼承它。包括fs
、net
、http
在內的,只要是支持事件響應的核心模塊都是EventEmitter
的子類。
原因有兩點。首先,具有某個實體功能的對象實現事件符合語義,事件的監聽和發射應該是一個對象的方法。其次JavaScript的對象機制是基于原型的,支持部分多重繼承,繼承EventEmitter
不會打亂對象原有的繼承關系。
文件系統fs
fs.readFile
fs.readFile(filename, [encoding], [callback(err, data)])
是最簡單的讀取文件的函數。
var fs = require("fs");
fs.readFile("server.js", "utf-8", function(err, data){
if (err){
console.log(err);
}else{
console.log(data);
}
})
fs.readFileSync
fs.readFileSync(filename, [encoding])
是fs.readFile
同步的版本。它接受的參數和fs.readFile
相同,而讀取到的文件內容會以函數返回值的形式返回。如果有錯誤發生,fs將會拋出異常,你需要使用try
和catch
捕捉并處理異常。
fs.open
fs.read
一般來說,除非必要,否則不要使用以上兩種方式讀取文件,因為它要求你手動管理緩沖區和文件指針,尤其是在你不知道文件大小的時候,這將會是一件很麻煩的事情。
HTTP服務器與客戶端
HTTP服務器
http.Server的事件
http.Server
是一個基于事件的HTTP服務器,所有的請求都被封裝為獨立的事件,開發者只需要對它的事件編寫響應函數即可實現HTTP服務器的所有功能。它繼承自EventEmitter
,提供了以下幾個事件:
- request:當客戶端請求到來時,該事件被觸發,提供兩個參數
req
和res
,分別是http.ServerRequest
和http.ServerResponse
的實例,表示請求和響應信息。 - connection:當TCP連接建立時,該事件被觸發,提供一個參數
socket
,為net.Socket
的實例。connection
事件的粒度要大于request
,因為客戶端在Keep-Alive模式下可能會在同一個連接內發送多次請求。 - close :當服務器關閉時,該事件被觸發。注意不是在用戶連接斷開時。
- checkContinue、upgrade、clientError事件。
最常用的就是request
了,因此http
提供了一個捷徑:http.createServer([requestListener])
,功能是創建一個HTTP服務器并將requestListener
作為request
事件的監聽函數。
http.ServerRequest
一般由http.Server
的request
事件發送,作為第一個參數傳遞,通常簡稱request或req。http.ServerRequest
提供了以下3個事件用于控制請求體傳輸:
- data :當請求體數據到來時,該事件被觸發。該事件提供一個參數chunk,表示接收到的數據。如果該事件沒有被監聽,那么請求體將會被拋棄。該事件可能會被調用多次。
- end :當請求體數據傳輸完成時,該事件被觸發,此后將不會再有數據到來。
- close:用戶當前請求結束時,該事件被觸發。不同于end,如果用戶強制終止了傳輸,也還是調用close。
獲取GET請求內容
url
模塊中的parse函數提供解析客戶端的表單請求。
// httpServerRequestGet.js
var http = require("http");
var url = require("url");
var util = require("util");
http.createServer(function(req, res){
res.writeHead(200, {"Conetnet-Type": "text/html"});
res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);
在瀏覽器中訪問http://127.0.0.1:3000/user?name=byvoid&email=byvoid@byvoid.com
通過url.parse
,原始的path
被解析為一個對象,其中query
就是我們所謂的GET請求的內容,而路徑則是pathname
。
獲取POST請求內容
// httpServerRequestPost.js
var http = require("http");
var querystring = require("querystring");
var util = require("util");
http.createServer(function(req, res){
var post = "";
req.on("data", function(chunk){
post += chunk
});
req.on("end", function(){
post = querystring.parse(post);
res.end(util.inspect(post));
});
}).listen(3000)
通過事件監聽函數。上面的代碼僅供理解使用,在實際編碼中不贊同這樣的做法。
http.ServerResponse
返回給客戶端的信息,也是由http.Server的request事件發送的,作為第二個參數傳遞,一般簡稱為response或res。
http.ServerResponse有三個重要的成員函數,用于返回響應頭、響應內容以及結束請求。
- response.writeHead(statusCode, [headers]):該函數在一個請求內最多只能調用一次。
- response.write(data, [encoding]):在response.end調用之前,response.write可以被多次調用。
- response.end([data], [encoding]): 結束響應,告知客戶端所有發送已經完成。
HTTP客戶端
http模塊提供了兩個函數http.request
和http.get
。
- http.request(options,callback)發起HTTP請求。接受兩個參數,
option
是一個類似關聯數組的對象,表示請求的參數,callback
是請求的回調函數。
// httpRequst.js
var http = require("http");
var querystring = require("querystring");
var content = querystring.stringify({
name: "byvoid",
email: "byvoid@byvoid.com",
address: "Tangshan",
});
var options = {
host: "127.0.0.1",
port: 3000,
path: '/user?hello=wen',
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": content.length
}
};
var req = http.request(options, function(res){
res.setEncoding("utf-8");
res.on("data", function(data){
console.log(data);
});
});
req.write(content);
req.end();
- http.get(options, callback)
http
模塊還提供了一個更加簡便的方法用于處理GET請求:http.get
。它是http.request
的簡化版,唯一的區別在于http.get
自動將請求方法設為了GET請求,同時不需要手動調用req.end()
。
http.ClientRequest
http.ClientRequest
是由http.request
或http.get
返回產生的對象,表示一個已經產生而且正在進行中的HTTP請求
http.ClientResponse
提供了三個事件data
、end
和close
,分別在數據到達、傳輸結束和連接結束時觸發,其中data
事件傳遞一個參數chunk
,表示接收到的數據。