Node.js學習(9)----核心模組
核心模組是 Node.js 的心臟,它由一些精簡而高效的庫組成,為 Node.js 提供了基本的API。主要內容包括:
全域性物件;
常用工具;
事件機制;
檔案系統訪問;
HTTP 伺服器與客戶端。
1、全域性物件
JavaScript 中有一個特殊的物件,稱為全域性物件(Global Object),它及其所有屬性都可以在程式的任何地方訪問,即全域性變數。在瀏覽器 JavaScript 中,通常 window 是全域性物件,而 Node.js 中的全域性物件是 global,所有全域性變數(除了 global 本身以外)都是 global物件的屬性。
1.1、全域性物件與全域性變數
global 最根本的作用是作為全域性變數的宿主。按照 ECMAScript 的定義,滿足以下條件的變數是全域性變數:
在最外層定義的變數;
全域性物件的屬性;
隱式定義的變數(未定義直接賦值的變數)。
當你定義一個全域性變數時,這個變數同時也會成為全域性物件的屬性,反之亦然。需要注意的是,在 Node.js 中你不可能在最外層定義變數,因為所有使用者程式碼都是屬於當前模組的,而模組本身不是最外層上下文。
永遠使用 var 定義變數以避免引入全域性變數,因為全域性變數會汙染名稱空間,提高程式碼的耦合風險。
1.2、process
process 是一個全域性變數,即 global 物件的屬性。它用於描述當前 Node.js 程序狀態
的物件,提供了一個與作業系統的簡單介面。通常在你寫本地命令列程式的時候,少不了要和它打交道。下面介紹 process 物件的一些最常用的成員方法。
process.argv是命令列引數陣列,第一個元素是 node,第二個元素是指令碼檔名,從第三個元素開始每個元素是一個執行引數。
console.log(process.argv);將以上程式碼儲存為 argv.js,通過以下命令執行:
process.stdout是標準輸出流,通常我們使用的 console.log() 向標準輸出列印字元,而 process.stdout.write() 函式提供了更底層的介面。
process.stdin是標準輸入流,初始時它是被暫停的,要想從標準輸入讀取資料,你必須恢復流,並手動編寫流的事件響應函式。
process.nextTick(callback)的功能是為事件迴圈設定一項任務,Node.js 會在下次事件迴圈調響應時呼叫 callback。process.stdin.resume(); process.stdin.on('data', function(data) { process.stdout.write('read from console: ' + data.toString()); });
1.3、console
console 用於提供控制檯標準輸出,它是由 Internet Explorer 的 JScript 引擎提供的除錯工具,後來逐漸成為瀏覽器的事實標準。Node.js 沿用了這個標準,提供與習慣行為一致的console 物件,用於向標準輸出流(stdout)或標準錯誤流(stderr)輸出字元。
console.log():向標準輸出流列印字元並以換行符結束。console.log 接受若干個引數,如果只有一個引數,則輸出這個引數的字串形式。如果有多個引數,則以類似於 C 語言 printf() 命令的格式輸出。第一個引數是一個字串,如果沒有引數,只打印一個換行。
console.log('Hello world');
console.error():與 console.log() 用法相同,只是向標準錯誤流輸出。
console.trace():向標準錯誤流輸出當前的呼叫棧。
2、常用工具util
util 是一個 Node.js 核心模組,提供常用函式的集合,用於彌補核心 JavaScript 的功能過於精簡的不足。
2.1、util.inherits
util.inherits(constructor, superConstructor)是一個實現物件間原型繼承的函式。JavaScript 的面向物件特性是基於原型的,與常見的基於類的不同。JavaScript 沒有提供物件繼承的語言級別特性,而是通過原型複製來實現的,在這裡介紹 util.inherits 的用法,示例如下:
var util=require('util');
function Base()
{
this.name='base';
this.base=1991;
this.sayHello=function(){
console.log('Hello '+this.name);
};
}
//這裡是Base原型定義的函式
Base.prototype.showName=function(){
console.log(this.name);
};
function Sub()
{
this.name='Sub';
}
util.inherits(Sub,Base);
var objBase=new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
var objSub=new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub);
我們定義了一個基礎物件 Base 和一個繼承自 Base 的 Sub,Base 有三個在建構函式內定義的屬性和一個原型中定義的函式,通過 util.inherits 實現繼承。執行結果如下:
base
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
Sub
{ name: 'Sub' }
注意,Sub 僅僅繼承了 Base 在原型中定義的函式,而建構函式內部創造的 base 屬性和 sayHello 函式都沒有被 Sub 繼承。同時,在原型中定義的屬性不會被 console.log 作為物件的屬性輸出。如果我們去掉 objSub.sayHello(); 這行的註釋,將會看到錯誤異常。
2.2、util.inspect
util.inspect(object,[showHidden],[depth],[colors])是一個將任意物件轉換
為字串的方法,通常用於除錯和錯誤輸出。它至少接受一個引數 object,即要轉換的物件。
showHidden 是一個可選引數,如果值為 true,將會輸出更多隱藏資訊。
depth 表示最大遞迴的層數,如果物件很複雜,你可以指定層數以控制輸出資訊的多少。如果不指定depth,預設會遞迴2層,指定為 null 表示將不限遞迴層數完整遍歷物件。
如果color 值為 true,輸出格式將會以 ANSI 顏色編碼,通常用於在終端顯示更漂亮的效果。
特別要指出的是,util.inspect 並不會簡單地直接把物件轉換為字串,即使該物件定義了 toString 方法也不會呼叫。
var util=require('util');
function Person()
{
this.name='MrGe';
this.toString=function(){
return this.name;
};
}
var obj=new Person();
console.log(util.inspect(obj));
console.log(util.inspect(obj,true));
結果輸出:
{ name: 'MrGe', toString: [Function] }
{ name: 'MrGe',
toString:
{ [Function]
[length]: 0,
[name]: '',
[arguments]: null,
[caller]: null,
[prototype]: { [constructor]: [Circular] } } }
3、事件驅動events
events 是 Node.js 最重要的模組,原因是 Node.js 本身架構就是事件式的,而它提供了唯一的介面,所以堪稱 Node.js 事件程式設計的基石。events 模組不僅用於使用者程式碼與 Node.js 下層事件迴圈的互動,還幾乎被所有的模組依賴。
3.1、事件發射器
events 模組只提供了一個物件: events.EventEmitter。EventEmitter 的核心就是事件發射與事件監聽器功能的封裝。EventEmitter 的每個事件由一個事件名和若干個引數組成,事件名是一個字串,通常表達一定的語義。對於每個事件,EventEmitter 支援若干個事件監聽器。當事件發射時,註冊到這個事件的事件監聽器被依次呼叫,事件引數作
為回撥函式引數傳遞。
var events=require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent',function(arg1,arg2){
console.log('listener1',arg1,arg2);
});
emitter.on('someEvent',function(arg1,arg2){
console.log('listener2',arg1,arg2);
});
emitter.emit('someEvent','Tome',1234567);
執行結果:
listener1 Tome 1234567
listener2 Tome 1234567
以上例子中,emitter 為事件 someEvent 註冊了兩個事件監聽器,然後發射了someEvent 事件。執行結果中可以看到兩個事件監聽器回撥函式被先後呼叫。
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,則移除指定事件的所有監聽器。
3.2、error 事件
EventEmitter 定義了一個特殊的事件 error,它包含了“錯誤”的語義,我們在遇到異常的時候通常會發射 error 事件。當 error 被髮射時,EventEmitter 規定如果沒有響應的監聽器,Node.js 會把它當作異常,退出程式並列印呼叫棧。我們一般要為會發射 error事件的物件設定監聽器,避免遇到錯誤後整個程式崩潰。
var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error');
3.3、繼承 EventEmitter
大多數時候我們不會直接使用 EventEmitter,而是在物件中繼承它。包括 fs、net、http 在內的,只要是支援事件響應的核心模組都是 EventEmitter 的子類。為什麼要這樣做呢?原因有兩點。首先,具有某個實體功能的物件實現事件符合語義,事件的監聽和發射應該是一個物件的方法。其次 JavaScript 的物件機制是基於原型的,支援部分多重繼承,繼承 EventEmitter 不會打亂物件原有的繼承關係。