當(dāng)前位置:首頁(yè)文章首頁(yè) IT學(xué)院 IT技術(shù)

Javascript的回調(diào)機(jī)制的經(jīng)典教程

作者:  來(lái)源:  發(fā)布時(shí)間:2011-6-6 10:17:59  點(diǎn)擊:

  由于其運(yùn)行環(huán)境的特殊性,Javascript大量使用異步的通信機(jī)制,凡是涉及到網(wǎng)絡(luò)調(diào)用和事件機(jī)制的代碼都會(huì)涉及。在異步通信的環(huán)境下編碼經(jīng)常會(huì)用到 回調(diào)函數(shù)。Javascript由于有函數(shù)式語(yǔ)言的一些特點(diǎn)使得它在Javascript里面實(shí)現(xiàn)回調(diào)函數(shù)非常的優(yōu)雅和自然,包括函數(shù)作為一級(jí)的對(duì)象、匿 名函數(shù)、閉包機(jī)制等。但是要體會(huì)到個(gè)中的優(yōu)雅,需要先融匯貫通這些機(jī)制。如果是初學(xué)者學(xué)習(xí)這些東西可能比有編程經(jīng)驗(yàn)的人少很多障礙,認(rèn)為事情本來(lái)就該是這 個(gè)樣子。但是,對(duì)于長(zhǎng)期使用過(guò)程式語(yǔ)言編碼(比如傳統(tǒng)的C/C++程序員),又沒(méi)有接觸過(guò)函數(shù)式語(yǔ)言的程序員來(lái)說(shuō),可能需要閱讀一道思維的小坎。這件事情 有時(shí)候會(huì)造成一定的困擾,因?yàn)?ldquo;老手”程序員會(huì)想:畢竟我已經(jīng)懂得一套能寫(xiě)程序的方法,大家都說(shuō)語(yǔ)言之間差別不重要,畢竟C++里面也有使用異步調(diào)用的時(shí) 候,主要注意一下語(yǔ)法的區(qū)別就好了。所以最終就變成了使用Javascript來(lái)模仿別的過(guò)程式語(yǔ)言,這樣的結(jié)果最終很有可能是寫(xiě)出很別扭的程序給自己添 堵。本文嘗試用幾個(gè)例子說(shuō)明異步通信的環(huán)境用Javascript寫(xiě)回調(diào)函數(shù)很使用類似C語(yǔ)言寫(xiě)回調(diào)函數(shù)的區(qū)別,以及為什么Javascript原生要更 適合做這件事情。(簡(jiǎn)單起見(jiàn),下面例子中的代碼均為偽代碼,并不一定嚴(yán)格符合C/C++或者Javascript的語(yǔ)法,但是筆者盡量寫(xiě)得與語(yǔ)法要求接 近。)

  

  我們首先從C/C++的同步調(diào)用開(kāi)始,假設(shè)我們要寫(xiě)一個(gè)函數(shù),向遠(yuǎn)方的服務(wù)器發(fā)送一個(gè)字符串形式得命令,并且從服務(wù)器得到一個(gè)字符串作為響應(yīng)。例1就展示了使用C語(yǔ)言在同步同步通信的機(jī)制下代碼的樣子。

  

  例1 使用C語(yǔ)言的編碼方式實(shí)現(xiàn)調(diào)用訪問(wèn)遠(yuǎn)程的接口

  view plaincopy to clipboardprint?

  01.//{{{get_data_v1

  02.int get_data_v1()

  03.{

  04. // 準(zhǔn)備數(shù)據(jù)

  05. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  06. char bufRcv[4096];

  07. // 建立連接

  08. socket s = new Socket();

  09. connnect(s, ip, port);

  10. // 發(fā)送數(shù)據(jù)

  11. send(s, bufCmd);

  12. // 接收數(shù)據(jù)

  13. recv(s, bufRcv);

  14. // 處理結(jié)果

  15. use(bufRcv);

  16. return 0;

  17.}

  18.//}}}

  在 例1中,get_data_v1執(zhí)行了準(zhǔn)備數(shù)據(jù)、創(chuàng)建了socket、建立連接、發(fā)送請(qǐng)求、接收響應(yīng)并最終使用use函數(shù)處理接收到的數(shù)據(jù),一切都顯得很 自然。為了方便說(shuō)明問(wèn)題,我們將這個(gè)通信的過(guò)程封裝一下,將整個(gè)建立連接并收發(fā)包的過(guò)程封裝成一個(gè)叫send_and_recv的函數(shù)。

  

  例2 將通信過(guò)程封裝成獨(dú)立的函數(shù),簡(jiǎn)化業(yè)務(wù)流程代碼

  view plaincopy to clipboardprint?

  01.//{{{get_data_v2

  02.// 發(fā)包收包的過(guò)程

  03.int send_and_recv(struct addr, char* bufCmd, char* bufRcv)

  04.{

  05. socket s = new Socket();

  06. connnect(s, addr.ip, addr.port);

  07. send(s, bufCmd);

  08. recv(s, bufRcv);

  09.}

  10.// 原來(lái)的業(yè)務(wù)流程

  11.int get_data_v2()

  12.{

  13. // 準(zhǔn)備數(shù)據(jù)

  14. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  15. char bufRecv[4096];

  16. // 通信,收發(fā)數(shù)據(jù)

  17. // addr={ip, port}

  18. send_and_recv(addr, bufCmd, bufRcv);

  19. // 處理結(jié)果

  20. use(bufRcv);

  21. return 0;

  22.}

  23.//}}}

  例 2和例1很類似,不過(guò)是對(duì)通信過(guò)程進(jìn)行封裝了,并且ip-port對(duì)也變成了一個(gè)叫addr的地址結(jié)構(gòu)體。改動(dòng)以后處理過(guò)程變得更簡(jiǎn)單,剩下準(zhǔn)備數(shù)據(jù)、通 信和處理結(jié)果三步。現(xiàn)在,我們開(kāi)始進(jìn)入正題,現(xiàn)在我們假設(shè)這個(gè)通信過(guò)程變成異步的,它接收一個(gè)回調(diào)函數(shù)用于處理取得的數(shù)據(jù)。如例3所示。

  

  例3 將通信過(guò)程變成異步調(diào)用

  view plaincopy to clipboardprint?

  01.//{{{get_data_v3

  02.// 變成異步調(diào)用以后,原來(lái)的調(diào)用過(guò)程分成了兩段

  03.// 前半段組裝參數(shù)調(diào)用發(fā)包過(guò)程

  04.// 后半段處理返

  05.// 這里假設(shè)send_and_recv是一個(gè)異步的網(wǎng)絡(luò)通信函數(shù)

  06.void get_data_v3()

  07.{

  08. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  09. char bufRcv[4096];

  10. send_and_recv_async(addr, bufCmd, bufRcv, callback);

  11.} // end of get_data_v3

  12.// 回調(diào)函數(shù)的定義

  13.int callback(char* bufRcv) {

  14. // 處理接收都的數(shù)據(jù)

  15. use(bufRcv);

  16. return 0;

  17.}

  18.//}}}

  在 例3中,假設(shè)使用了一個(gè)異步的通信過(guò)程send_and_recv_async,最后一個(gè)參數(shù)callback是一個(gè)回調(diào)函數(shù)指針。然后,當(dāng)接收到響應(yīng)以 后,send_and_recv_async會(huì)調(diào)用callback并傳入接收到的數(shù)據(jù)。相比例2,這個(gè)get_data的過(guò)程被異步通信過(guò)程一分為二: 前半段為準(zhǔn)備請(qǐng)求,后半段是處理結(jié)果。事實(shí)上,對(duì)將同步通信方式變成異步以后,都會(huì)涉及到將原來(lái)完整處理過(guò)程一分為二的問(wèn)題。在兩段程序沒(méi)有什么相互依賴 的情況下,這樣的分解不會(huì)造成什么問(wèn)題。但是,如果處理結(jié)果的過(guò)程依賴于一些外部參數(shù),那么情況就會(huì)變得很復(fù)雜。我們先來(lái)看看在同步通信的情況下,程序的 樣子,見(jiàn)例4。

  

  例4 假設(shè)處理結(jié)果的時(shí)候依賴外部參數(shù)

  view plaincopy to clipboardprint?

  01.//{{{get_data_v4

  02.// 這里原來(lái)的業(yè)務(wù)流程需要外部傳進(jìn)來(lái)的兩個(gè)參數(shù)(a,b)來(lái)決定如何處理結(jié)果

  03.int get_data_v4(int a, int b)

  04.{

  05. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  06. char bufRcv[4096];

  07. send_and_recv(addr, bufCmd, bufRcv);

  08. // 處理過(guò)程依賴于外部傳進(jìn)來(lái)的參數(shù)a和b

  09. use(bufRcv, a, b);

  10. return 0;

  11.}

  12.//}}}

  在例4中,我們的結(jié)果處理過(guò)程use依賴于傳入的兩個(gè)參數(shù)a和b,F(xiàn)在我們來(lái)看看例4的程序如果使用異步通信會(huì)怎樣,見(jiàn)例5。

  

  例5 加上參數(shù)依賴后再變成異步調(diào)用

  view plaincopy to clipboardprint?

  01.// 版本a

  02.//{{{get_data_v5

  03.// 需要參數(shù)的異步調(diào)用需要將參數(shù)透?jìng)鞯胶蟀攵蔚幕卣{(diào)函數(shù)中

  04.void get_data_v5a(int a, int b)

  05.{

  06. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  07. char bufRcv[4096];

  08. send_and_recv_async(addr, bufCmd, bufRcv, callbacka, a, b);

  09.} // end of get_data_v5a

  10.// 回調(diào)函數(shù)的定義

  11.int callbacka(char* bufRcv, int a, int b) {

  12. use(bufRcv, a, b);

  13. return 0;

  14.}

  15.// 版本b

  16.int g_a;

  17.int g_b;

  18.void get_data_v5b(int a, int b)

  19.{

  20. g_a = a;

  21. g_b = b;

  22. char bufCmd[]="cmd=1001&uin=123456?m=abc";

  23. char bufRcv[4096];

  24. send_and_recv_async(addr, bufCmd, bufRcv, callbackb);

  25.} // end of get_data_v5b

  26.// 回調(diào)函數(shù)的定義

  27.int callbacka(char* bufRcv, int a, int b) {

  28.int callbackb(char* bufRcv) {

  29. use(bufRcv, g_a, g_b);

文章評(píng)論

軟件按字母排列: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z