Javascript的回調(diào)機(jī)制的經(jīng)典教程
31.}
32.//}}}
例 5中有兩個(gè)版本,get_data_v5a假設(shè)了通信機(jī)制可以透?jìng)鱝和b兩個(gè)參數(shù)給回調(diào)函數(shù),get_data_v5b則使用了兩個(gè)全局變量來(lái)傳遞處理結(jié) 果所需的參數(shù)。兩個(gè)都不見(jiàn)得是很好的方法,get_data_v5a的問(wèn)題是,異步通信的機(jī)制不見(jiàn)得能提供這種透?jìng)鳈C(jī)制,除非程序員自己封裝;即使程序員 自己封裝,那也意味著如果要實(shí)現(xiàn)多個(gè)處理數(shù)據(jù)的過(guò)程(像get_data)那就要實(shí)現(xiàn)多個(gè)異步調(diào)用的過(guò)程(send_and_recv_async),代 碼復(fù)雜且復(fù)用性差不好維護(hù)。而全局變量的版本也好不到哪里去,使用這種全局的機(jī)制,意味著不必要的信息暴露,也就有被別的地方錯(cuò)修改的問(wèn)題,同時(shí)這個(gè)函數(shù) 還變成不可重入的。即使將全局機(jī)制封裝在一個(gè)類里面,每次初始化一個(gè)對(duì)象,可以改善依然不能解決信息暴露的問(wèn)題,同時(shí)還帶來(lái)了管理這多個(gè)對(duì)象的復(fù)雜性。
兩種方法相比而言,貌似透?jìng)鞯臋C(jī)制要稍好一些。我們對(duì)get_data_v5a略做修改,使得它通信過(guò)程能夠有更廣泛的復(fù)用。
例6 使用一個(gè)closure對(duì)象打包過(guò)程中的參數(shù)
view plaincopy to clipboardprint?
01.//{{{get_data_v6
02.// 為了統(tǒng)一回調(diào)函數(shù)的形式并且縮短回調(diào)的參數(shù)列表,將這種需要透?jìng)鞯膮?shù)只有一個(gè)
03.// 統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)打包
04.void get_data_v6(int a, int b)
05.{
06. // 準(zhǔn)備數(shù)據(jù)
07. char bufCmd[]="cmd=1001&uin=123456?m=abc";
08. char bufRcv[4096];
09. // 打包處理結(jié)果所需要的參數(shù)
10. closure.a = a;
11. closure.b = b;
12. // 通信
13. send_and_recv_async(addr, bufCmd, bufRcv, callback, closure);
14.} // end of get_data_v6
15.// 回調(diào)函數(shù)的定義
16.int callback(char* bufRcv, struct closure) {
17. // 處理結(jié)果
18. use(bufRcv, closure.a, closure.b);
19. return 0;
20.}
21.//}}}
例 6里面使用了一個(gè)叫closure的結(jié)構(gòu),假設(shè)這個(gè)結(jié)構(gòu)是個(gè)通用的數(shù)據(jù)容器,可以容納我們使用的個(gè)中類型的任意數(shù)量的參數(shù)。增加了這一個(gè)萬(wàn)能的數(shù)據(jù)容器參 數(shù)以后,異步通信過(guò)程只要能透?jìng)鬟@么一個(gè)數(shù)據(jù)容器就能夠很好支持個(gè)中各樣的參數(shù)透?jìng)鞯男枨蟆_@個(gè)數(shù)據(jù)容器由于是在get_data函數(shù)內(nèi)部產(chǎn)生的局部變 量,不會(huì)污染全局?jǐn)?shù)據(jù)或者比get_data更大的作用域。這種受限的可見(jiàn)性不僅提高了代碼的可維護(hù)性,還恢復(fù)了函數(shù)的可重入性。
至此我 們關(guān)于回調(diào)機(jī)制的實(shí)現(xiàn)的假想代碼可以說(shuō)已經(jīng)達(dá)到比較優(yōu)雅的程度了,僅僅還有一朵小烏云。那就是我們忽略了C/C++語(yǔ)言里面并沒(méi)有原生實(shí)現(xiàn)這個(gè)超級(jí)結(jié)構(gòu), 同樣我們依然還有一點(diǎn)點(diǎn)麻煩就是還需要指定要透?jìng)鞯膮?shù)?紤]到原本從準(zhǔn)備數(shù)據(jù)到通信再到處理結(jié)果是一個(gè)完整統(tǒng)一的過(guò)程,原本不需要區(qū)分什么數(shù)據(jù)是前半端 使用的什么數(shù)據(jù)是后半段使用的,只要腳氣怎么治療讓前半端和后半段共享一個(gè)上下文在大部分情況下就能滿足需求了。所以現(xiàn)實(shí)情況下我們只能做一些妥協(xié),使用個(gè)中折衷方案 來(lái)使得程序能運(yùn)行起來(lái)。同樣,考慮到回調(diào)函數(shù)和啟動(dòng)函數(shù)的關(guān)系,給回調(diào)函數(shù)命名也不是那么優(yōu)雅的事情,因?yàn)楫吘顾鼈冎皇峭粋(gè)過(guò)程的兩半,卻要使用兩個(gè)名 字,合理一點(diǎn)就應(yīng)該叫g(shù)et_data_first和get_data_second,或者get_data_trigger和 get_data_result_handler。如果接口多的話,就會(huì)有很多這種某過(guò)程first和某過(guò)程second,或者某過(guò)程trigger和某 過(guò)程result_handler。能不能某過(guò)程就象同步那樣使用一個(gè)名字呢?我們的設(shè)想真的就沒(méi)有辦法達(dá)到嗎?答案是否定的,在Javascript能 夠幫助我們實(shí)現(xiàn)我們所有的設(shè)想。見(jiàn)例7。
例7 Javascript的異步調(diào)用
view plaincopy to clipboardprint?
01.//{{{get_data_js
02.//
03.// 寫(xiě)成Javascript代碼就變成現(xiàn)在這個(gè)樣子
04.// url對(duì)應(yīng)之前的addr
05.// 使用匿名函數(shù)代替原來(lái)命名的callback定義
06.// 原生支持閉包c(diǎn)losure
07.//
08.function get_data_js(a, b)
09.{
10. var bufCmd = "cmd=1001&uin=123456?m=abc";
11. var bufRcv;
12. send_and_recv_with_xhr(/*addr*/url, bufCmd, bufRcv, /*callback*/
13. function(bufRcv/*, closure*/) {
14. use(bufRcv, /*closure.*/a, /*closure.*/b);
15. return 0;
16. }
17. );
18.}
19.//}}}
例 7是使用Javascript實(shí)現(xiàn)類似例6的功能,僅僅存在一些細(xì)微的差別。例6的場(chǎng)景下可能更多使用TCP或者UDP作為通信協(xié)議,而在例7使用的則是 瀏覽器提供的XHR對(duì)象實(shí)現(xiàn)的HTTP協(xié)議。這點(diǎn)差別并不會(huì)影響我們對(duì)于異步通信下回調(diào)函數(shù)實(shí)現(xiàn)機(jī)制的討論,只要他們的通信機(jī)制都是異步的就可以了。例7 中使用注釋的形式標(biāo)注了例6里面使用的一些參數(shù)的名字以暗示它們的對(duì)應(yīng)關(guān)系,方便比較這兩個(gè)例子。我們看到了,在Javascript里面我們所有的設(shè)想 都變成了現(xiàn)實(shí)。(1)首先關(guān)于能夠透?jìng)饕磺械某?jí)結(jié)構(gòu),Javascript中實(shí)現(xiàn)了閉包的機(jī)制,保證了在這種內(nèi)部的函數(shù)對(duì)象可以訪問(wèn)到定義它的環(huán)境能訪 問(wèn)到的所有數(shù)據(jù),也就是在例7中的匿名回調(diào)函數(shù)可以訪問(wèn)到get_data_js中能訪問(wèn)到的所有數(shù)據(jù)。當(dāng)然,這里重要的是局部數(shù)據(jù),如a和b。如果是全 局?jǐn)?shù)據(jù)的話左旋肉堿真的有用嗎并不需要通過(guò)閉包也能訪問(wèn)到。而且這個(gè)過(guò)程是Javascript的運(yùn)行環(huán)境提供的,對(duì)于程序員是透明的,程序員并不需要指定哪些參數(shù)需要透 傳。(2)不需要再為回調(diào)函數(shù)命名,因?yàn)镴avascript支持匿名函數(shù)的定義,可以像定義變量一樣定義函數(shù)。而這個(gè)最終導(dǎo)致了我們?cè)谑褂卯惒酵ㄐ艡C(jī)制 的時(shí)候和使用同步的通信機(jī)制及其接近,沒(méi)有多余的名字,沒(méi)有不必要的可見(jiàn)性。