Nodejs核心模塊 PART2

4.2 常用工具util

util是Nodejs核心模塊,提供常用函數的集合,用於彌補JS的功能過於精簡的不足。

使用util模塊首先需要導入它

var util = require('util');

4.2.1 util.inherits(constructor,superConstructor)

util.inherits(constructor,superConstructor)是一個實現對象間原形繼承的函數。用法如下:

var util = require('util');

function Animal() {
    this.nickName = "ANIMAL";
   this.sayHello = function () {
    console.log("hello, We are " + this.nickName);
}
}

Animal.prototype.sayName = function () {
    console.log("We are " + this.nickName);
};

function Dog () {
    this.nickName = "DOG"
}

var animal = new Animal();
animal.sayName();
animal.sayHello();
console.log(animal);

util.inherits(Dog,Animal);

var dog = new Dog();
dog.sayName();
console.log(dog);
// dog.sayHello();

在Animal類的構造函數中,有一個叫做nickName的屬性和一個叫做sayHello的方法。同時,我們使用prototype爲Animal類定義了一個叫做sayName的方法。運行程序,輸出:

We are ANIMAL
hello, We are ANIMAL
Animal { nickName: 'ANIMAL', sayHello: [Function] }
We are DOG
Dog { nickName: 'DOG' }

通過打印Dog的構造器可見,使用inherits方法可使子類繼承父類原型中的方法,即sayName方法,但不可繼承在父類構造器中定義的東西,即nickName和sayHello。最後一行註釋掉的sayHello如果打開,程序會報錯。注意,第一個參數是子類,第二個參數是超類(即父類),不要寫反了。

4.2.2 util.inspect(obj)

util.inpect(obj,[showHidden], [depth], [color])可以將任意對象轉化成字符串(這種方式並非簡單的直接把對象轉化成字串,所以不會理會里面的toString方法)。

  1. obj即你要轉化成字符串的對象
  2. showHidden是一個可選參數,默認是fasle,如果值爲true,將會輸出更多隱藏信息。(主要是輸出對象成員方法的詳細信息,比如參數信息等等,但對對象的屬性作用不大)
  3. depth是一個可選參數,表示遍歷的層數,默認2層(也就是能夠詳細的遍歷打印出成員屬性的屬性的屬性的值),如果設置爲null則將不限遞歸層數完整遍歷對象。
  4. color是一個可選參數,如果設置爲true,終端則會使用ANSI顏色編碼使效果更佳。

下面的代碼:

var util = require('util');

var Tom = {
    name: "Tom",
    sayName:function () {
    console.log(this.name);
    },
    age:15,
    family: {
        parent: {father: "Old Tom", mother: "MS.Tom"},
    pet: {
        dog: {"husky": "dingDing"}
    }
    }
};

console.log(util.inspect(Tom));
console.log(util.inspect(Tom,true));
console.log(util.inspect(Tom,false,null));
console.log(util.inspect(Tom,false,null,true));

運行結果爲:

{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: [Object] } } }
{ name: 'Tom',
  sayName: 
   { [Function]
 [length]: 0,
 [name]: '',
 [arguments]: null,
 [caller]: null,
 [prototype]: { [constructor]: [Circular] } },
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: [Object] } } }
{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: { husky: 'dingDing' } } } }
{ name: 'Tom',
  sayName: [Function],
  age: 15,
  family: 
   { parent: { father: 'Old Tom', mother: 'MS.Tom' },
 pet: { dog: { husky: 'dingDing' } } } }

注:最後的代碼函數、屬性的值等是彩色的,在md裏顯示不出來。

util.inspect與JSON.stingify的區別在於inspect顯得更加細緻一些,並且會有格式縮進。後者直接循環遍歷所有層,沒有多餘的設置,沒有縮進,整個字串都出現在一行

還有一點需要說明,因爲這個函數參數比較多,如果你想設置後面的參數,前面的參數必須要設置,可以設置成和默認值相同。

4.2.3 util.isXXX()

util對象還定義了很多判斷數據類型的函數,都是是某一類型放回true,否則返回false,這裏就不做示範了。

但要注意,在使用util.isDate()函數時,如果這樣

util.isDate(Date());

會返回false,因爲Date()產生的被認爲是一個字串。需要在前面加上new,才被認爲是一個日期對象。

4.3 事件驅動

events是nodejs最重要的模塊,沒有之一。原因是Nodejs本身的架構是事件式的,而它提供了唯一的接口,所以堪稱Nodejs事件編程的基礎。events模塊不僅用於代碼與Nodejs下層事件循環的交互,還幾乎被所有模塊依賴

在使用events模塊時,首先需要導入它,實際上,這個模塊暴露的是EventEmitter構造函數(是使用第一種模塊導出模型暴露的,也就是exports.XXX = XXX)。綁定事件和發射事件都是在這個構造函數的實例化對象上進行的。

var events = require('events');
var EventEmitter = events.EventEmitter;
var event = new EventEmitter();

4.3.1 事件發射器

4.3.1.1 事件發射了!!

events模塊只暴露了一個構造函數:events.EventEmitter(),它的核心就是事件發射(觸發)和事件監聽功能的封裝。

下面的代碼將會幫助你理解events模塊最簡單的用法——註冊事件和發射事件:

var events = require('events')
var EventEmitter =events.EventEmitter;
var event = new EventEmitter();

// 註冊一個最簡單的事件監聽器
event.on("sayHello",function (arg){
    console.log("hello " + arg);
});

event.emit("sayHello", "world");

運行上面的代碼,輸出hello world。

原理是在event註冊了一個“sayHello”事件的監聽器,然後我們通過emit發射sayHello事件,觸發該監聽器,執行裏面設置的回調函數。下面我們將會介紹EventEmitter常用的API(下面代碼中的event是EventEmitter的實例化對象)

4.3.1.2 註冊事件

(1)最簡單的註冊事件的方式如下:

event.on(事件名稱字符串,函數)  或  event.addListener(事件名稱字符串,函數)

(2)你還可以once方法設置單次監聽,這種監聽器最多隻會觸發一次,觸發後該監聽器會立即失效。

event.once(事件名稱字符串,函數)
4.3.1.3 發射事件:
event.emit(事件名稱字符串,[監聽器回調函數參數1], [監聽器回調函數參數2] ……)

值得注意的是,EventEmitter支持爲每一個事件(名字相同,我們就認爲是一個事件)註冊若干個事件監聽器。在默認狀態下,每個事件最多支持10個事件監聽器來“監視”它,超過仍可執行,但會拋出警告。可以通過event.setMaxListeners(n)可以設置最多綁定事件監聽器的上限,但建議不要超過十個,否則有可能會導致內存泄露。當事件發射時,註冊到這個事件的監聽器依次被調用。

另外說一句,如果你熟悉了在瀏覽器上點擊某個按鈕觸發某個事件的機制,那麼你可能不得不花一些時間去適應node的這種用代碼發射事件的機制了。

4.3.1.4 解除事件綁定

當解除事件綁定時,我們面對有兩種情況:一種是解除某一個事件的某一個監聽器;第二種是完全刪除某一個事件的所有監聽器。

(1)我們可以先從簡單的完全刪除某一個事件的所有監聽器入手:

event.removeAllListener("事件名")

(2)如果你想要刪除某一個事件的某一個監聽器,按照正常的邏輯,需要兩個參數:一個是事件的名稱,第二個是監聽器名稱。第一個參數好說,但有沒有搞錯,我們根本不知道監聽器叫什麼好不好。第二個參數應該選擇什麼呢?Nodejs是這樣設計的:第二個參數填監聽器回調函數的名稱。所以,如果你考慮到以後有可能存在刪除某一事件的某一監聽器的時候,在註冊監聽器的時候,回調函數就不要使用匿名函數,而採取命名函數的方式,下面的代碼是一個簡單的例子:

var EventEmitter = require("events").EventEmitter;

var event = new EventEmitter();

event.on("sayHello", sayHello);

function sayHello() {
    console.log("hello");
}

console.log(EventEmitter.listenerCount(event, "sayHello")); // =>1

event.removeListener("sayHello", sayHello);

console.log(EventEmitter.listenerCount(event, "sayHello")); // =>0

這裏我們“順便”隆重介紹一個EventEmitter的類方法:

EventEmitter.listenerCount(event, "事件名")

這個方法返回你在某一個EventEmitter實例上註冊的某一個事件的監聽器數量。它接收兩個參數,第一個是EventEmitter的實例化對象,也就是你註冊事件監聽器的對象。第二個參數是事件名。

4.3.2 error事件

EventEmitter 定義了一個特殊的事件 error,它包含了錯誤的語義,我們在遇到異常的時候通常會觸發 error 事件(是我們手動通過代碼去觸發,而不是程序遇到異常就觸發error事件!)。當 error 被觸發時,EventEmitter規定如果沒有響應的監聽器,Node.js 會把它當作異常,退出程序並輸出錯誤信息。我們一般要爲會觸發 error 事件的對象設置監聽器,避免遇到錯誤後整個程序崩潰。例如:

var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.emit('error'); 

會提出程序,並打印調用棧。

但注意上面加粗的文字,下面的代碼照樣會報錯,起不到我們想像的作用:

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

event.on("sayHello", function () {
    d===d // 錯誤的代碼
});

event.on("error", function () {
    console.log("錯誤已處理");
});

event.emit("sayHello");

4.3.3 繼承EventEmitter

大多數時候我們不會直接使用 EventEmitter,而是在對象中繼承它。包括 fs、net、 http 在內的,只要是支持事件響應的核心模塊都是 EventEmitter的子類。

爲什麼要這樣做呢?原因有兩點:
首先,具有某個實體功能的對象實現事件符合語義, 事件的監聽和發射應該是一個對象的方法。

其次 JavaScript 的對象機制是基於原型的,支持部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關係。

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