nodejs學習筆記(三)——javascript的回調與nodejs的事件循環

nodejs學習筆記(三)——javascript的回調與nodejs的事件循環。舉個簡單的例子說明一下回調:我們有兩個函數a和b,在程序運行過程中我們希望在函數a運行完成以後再運行函數b,一般會這樣寫:

function a(){
	console.log("enter function a");
}

function b(){
	console.log("enter function b");
}

a();
b();

把上例代碼保存在test6.js中,然後通過node test6.js命令來驗證(字符串“enter function a”會先於字符串“enter function b”打印):

但是在函數a包含異步請求的情況下就可能發生函數a未執行完成就開始執行函數b的情況。下面我們來構造一下這種場景(這裡繼續使用上一篇筆記裡的web application例子),增加一個html頁面:



增加的html名稱和位置如下:

啟動tomcat,然後訪問https://localhost:8080/firstWebProject/test6.html(這裡補充說明一下筆者使用的瀏覽器信息:chrome53.0.2785.101)打開控制臺查看打印信息:

可以看到雖然在代碼順序上函數a應該先執行,但是實際上函數b的打印信息在函數a未執行完成時就打印瞭。那麼在這種情況下我們怎麼保證函數執行的時序呢,一般我們會這麼幹(把html中的javascript代碼稍微調整下):



<script src=”lib/jquery-3.0.0.min.js”></script>
即不直接調用函數a後接著調用函數b,而是在函數a異步請求完成時調用函數b,然後刷新一下剛才訪問的頁面:

從打印信息可以看到是先執行瞭函數a,然後才執行瞭函數b。這裡其實已經有瞭回調的雛形,但是硬編碼把函數b寫在函數a裡面顯然並不合適,比如在函數a執行完成後需要根據情況調用函數b還是函數c,在硬編碼寫回調的基礎上修改則工作量較大且難以維護。接下來我們在硬編碼寫回調的基礎上再優化一把:



<script src=”lib/jquery-3.0.0.min.js”></script>
可以看到結果與上面硬編碼寫回調是相同的:

和硬編碼唯一的不同就是我們在聲明函數a時同時聲明瞭函數a需要一個回調函數參數,並在函數a內部合適的位置(例子中是發起請求完成後)調用回調函數,且在調用函數a時再傳入回調函數參數b,來達到函數a運行完成後再運行函數b的結果。

在定義函數a的時候隻定義瞭回調函數名,那麼如果回調函數需要傳入在函數a內沒有的信息作為參數該如何處理?下面再給出解決這個問題的一個例子(簡單的說,就是在調用函數a的時候就把回調函數所需的參數傳進去,即函數a的參數應包含函數b的參數):



<script src=”lib/jquery-3.0.0.min.js”></script>
執行結果如下圖:

那如果回調函數的參數個數不定的情況該如何處理(即回調函數b有一個參數,回調函數c有兩個參數,這兩個函數都有可能是函數a的回調函數,此時我們在定義函數a時就不確定該為回調函數構造幾個參數瞭)?非常簡單,在定義回調函數的時候可以把參數定義成一個對象(一個參數則對象包含一個鍵值對,兩個參數則對象包含兩個鍵值對,以此類推),函數a隻需要為回調函數定義一個對象參數,在調用回調函數的時候把這個對象參數傳給回調函數,由回調函數內部來解析這個對象參數。

另外要註意的是:回調函數並不意味它必須在前一個函數執行完成後再執行,它在前一個函數的哪個位置執行是可控的。

說完瞭原生javascript的回調,再說一下nodejs的事件循環模塊——events。筆者認為events實質上是對回調的一個系統管理,所以把它和回調放在一個章節來說(實際上shell腳本裡的hook鉤子、java裡的監聽都算是回調管理,events的使用看起來更接近java的監聽)。下面的例子來源於runoob,筆者調整補充瞭部分註釋:

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

// 回調函數1,在nodejs中為監聽器
var listener1 = function listener1() {
   console.log('run listener1');
}

// 回調函數2
var listener2 = function listener2() {
  console.log('run listener2');
}

// 為回調函數1綁定監聽事件,當觸發監聽事件時執行回調函數
eventEmitter.addListener('connection', listener1);

// 為回調函數2綁定監聽事件,當觸發監聽事件時執行回調函數,addListener和on效果相同
eventEmitter.on('connection', listener2);

// 讀取事件管理對象某一監聽事件上的回調函數個數
var eventListeners = events.EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + " listener");

// 觸發監聽事件 
eventEmitter.emit('connection');

// 移除監聽事件綁定的某一回調函數
eventEmitter.removeListener('connection', listener1);
console.log("listener1 remove");

// 觸發監聽事件
eventEmitter.emit('connection');

// 讀取事件管理對象某一監聽事件上的回調函數個數
eventListeners = events.EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + " listener");

console.log("complete");

把上例代碼保存在test7.js中,然後通過node test7.js命令來驗證:

可以看到當我們觸發監聽事件後,註冊到監聽事件的回調函數被執行瞭(當動態移除監聽事件綁定的回調函數後,隻有剩下的綁定回調函數被執行瞭)。

相比之前例子中的回調,events加入瞭一個監聽事件的中間層,用於鏈接回調函數和調用者,這樣調用者和回調函數就無需互相感知瞭(即我們之前例子裡的函數a無需在函數內部寫回調函數的調用瞭,甚至不感知有回調函數的存在——監聽事件可以不綁定任何回調函數),取而代之的行為是觸發一下監聽事件。

nodejs的events模塊的API:https://nodejs.org/api/events.html

發佈留言