Javascript的回调机制的经典教程
31.}
32.//}}}
例 5中有两个版本,get_data_v5a假设了通信机制可以透传a和b两个参数给回调函数,get_data_v5b则使用了两个全局变量来传递处理结 果所需的参数。两个都不见得是很好的方法,get_data_v5a的问题是,异步通信的机制不见得能提供这种透传机制,除非程序员自己封装;即使程序员 自己封装,那也意味着如果要实现多个处理数据的过程(像get_data)那就要实现多个异步调用的过程(send_and_recv_async),代 码复杂且复用性差不好维护。而全局变量的版本也好不到哪里去,使用这种全局的机制,意味着不必要的信息暴露,也就有被别的地方错修改的问题,同时这个函数 还变成不可重入的。即使将全局机制封装在一个类里面,每次初始化一个对象,可以改善依然不能解决信息暴露的问题,同时还带来了管理这多个对象的复杂性。
两种方法相比而言,貌似透传的机制要稍好一些。我们对get_data_v5a略做修改,使得它通信过程能够有更广泛的复用。
例6 使用一个closure对象打包过程中的参数
view plaincopy to clipboardprint?
01.//{{{get_data_v6
02.// 为了统一回调函数的形式并且缩短回调的参数列表,将这种需要透传的参数只有一个
03.// 统一的数据结构打包
04.void get_data_v6(int a, int b)
05.{
06. // 准备数据
07. char bufCmd[]="cmd=1001&uin=123456?m=abc";
08. char bufRcv[4096];
09. // 打包处理结果所需要的参数
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.// 回调函数的定义
16.int callback(char* bufRcv, struct closure) {
17. // 处理结果
18. use(bufRcv, closure.a, closure.b);
19. return 0;
20.}
21.//}}}
例 6里面使用了一个叫closure的结构,假设这个结构是个通用的数据容器,可以容纳我们使用的个中类型的任意数量的参数。增加了这一个万能的数据容器参 数以后,异步通信过程只要能透传这么一个数据容器就能够很好支持个中各样的参数透传的需求。这个数据容器由于是在get_data函数内部产生的局部变 量,不会污染全局数据或者比get_data更大的作用域。这种受限的可见性不仅提高了代码的可维护性,还恢复了函数的可重入性。
至此我 们关于回调机制的实现的假想代码可以说已经达到比较优雅的程度了,仅仅还有一朵小乌云。那就是我们忽略了C/C++语言里面并没有原生实现这个超级结构, 同样我们依然还有一点点麻烦就是还需要指定要透传的参数。考虑到原本从准备数据到通信再到处理结果是一个完整统一的过程,原本不需要区分什么数据是前半端 使用的什么数据是后半段使用的,只要脚气怎么治疗让前半端和后半段共享一个上下文在大部分情况下就能满足需求了。所以现实情况下我们只能做一些妥协,使用个中折衷方案 来使得程序能运行起来。同样,考虑到回调函数和启动函数的关系,给回调函数命名也不是那么优雅的事情,因为毕竟它们只是同一个过程的两半,却要使用两个名 字,合理一点就应该叫get_data_first和get_data_second,或者get_data_trigger和 get_data_result_handler。如果接口多的话,就会有很多这种某过程first和某过程second,或者某过程trigger和某 过程result_handler。能不能某过程就象同步那样使用一个名字呢?我们的设想真的就没有办法达到吗?答案是否定的,在Javascript能 够帮助我们实现我们所有的设想。见例7。
例7 Javascript的异步调用
view plaincopy to clipboardprint?
01.//{{{get_data_js
02.//
03.// 写成Javascript代码就变成现在这个样子
04.// url对应之前的addr
05.// 使用匿名函数代替原来命名的callback定义
06.// 原生支持闭包closure
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实现类似例6的功能,仅仅存在一些细微的差别。例6的场景下可能更多使用TCP或者UDP作为通信协议,而在例7使用的则是 浏览器提供的XHR对象实现的HTTP协议。这点差别并不会影响我们对于异步通信下回调函数实现机制的讨论,只要他们的通信机制都是异步的就可以了。例7 中使用注释的形式标注了例6里面使用的一些参数的名字以暗示它们的对应关系,方便比较这两个例子。我们看到了,在Javascript里面我们所有的设想 都变成了现实。(1)首先关于能够透传一切的超级结构,Javascript中实现了闭包的机制,保证了在这种内部的函数对象可以访问到定义它的环境能访 问到的所有数据,也就是在例7中的匿名回调函数可以访问到get_data_js中能访问到的所有数据。当然,这里重要的是局部数据,如a和b。如果是全 局数据的话左旋肉碱真的有用吗并不需要通过闭包也能访问到。而且这个过程是Javascript的运行环境提供的,对于程序员是透明的,程序员并不需要指定哪些参数需要透 传。(2)不需要再为回调函数命名,因为Javascript支持匿名函数的定义,可以像定义变量一样定义函数。而这个最终导致了我们在使用异步通信机制 的时候和使用同步的通信机制及其接近,没有多余的名字,没有不必要的可见性。