dojo.connect 函数用来处理某一个实体上发生的事件,不管处理的是 DOM 事件还是用户自定义事件,事件源和事件处理函数是通过 dojo.connect 直接绑定在一起的,Dojo 提供的另一种事件处理模式使得事件源和事件处理函数并不直接关联,这就是“订阅/发布”。“订阅/发布”模式可以说是一个预订系统,用户先预定自己感兴趣的主题,当此类主题发布时,将在第一时间得到通知。这跟我们熟知的网上购物系统不一样,网上购物是先有物,用户再去买,而在订阅/发布模式下,预订的时候并不确定此类主题是否已存在,以后是否会发布。只是在主题发布之后,会立即得到通知。订阅/发布模式是靠主题把事件和事件处理函数联系起来的。在 Dojo 中,跟主题订阅 / 发布有关的函数有三个:
- dojo.subscribe = function(/*String*/ topic, /*Object|null*/ context, /*String|Function*/ method)
subscribe 函数用来订阅某一主题;参数 topic 表示主题名字,是一个字符串; context 是接收到主题后调用的事件处理函数所在的对象,function 是事件处理函数名。
- dojo.unsubscribe = function(/*Handle*/ handle)
取消对于某一主题的订阅;参数 handle 是 dojo.subscribe 返回的句柄,跟 dojo.connect 与 dojo.disconnect 的工作方式一样。
- dojo.publish = function(/*String*/ topic, /*Array*/ args)
发布某一主题;参数 topic 是主题的名字,args 表示要传递给主题处理函数的参数,它是一个数组,可以通过它传递多个参数给事件处理函数。
订阅 / 发布模式看上去很神秘,但实现是比较简单的。 dojo 维护了一个主题列表,用户订阅某一主题时,即把此主题及其处理函数添加到主题列表中。当有此类主题发布时,跟这一主题相关的处理函数会被顺序调用。注意:如果用户使用了相同的处理函数重复订阅某一主题两次,在主题列表中这是不同的两项,只是他们都对同一主题感兴趣。当此类主题发布时,这两个处理函数都会被调用,而不会出现第二个处理函数覆盖第一个处理函数的状况。清单 7 的例子展示了订阅 / 发布模式是如何工作的。
清单 7
<html> <head> <script type="text/javascript" djConfig="parseOnLoad: true, isDebug: true" src="../dojo/dojo/dojo.js"></script> </head> <body> <script> var NewsReporter = { sports : function(message) { for (var i = 0; i < message.length; i++) console.info("sports:" + message[i]); }, entertainment: function(message) { for (var i = 0; i < message.length; i++) console.info("entertainment:" + message[i]); } , mixed: function (sportsNews, entermaintainNews) { console.info("mixed"); this.sports(sportsNews); this.entertainment(entermaintainNews); } } /*first subscribe*/ handle1 = dojo.subscribe("sports news", NewsReporter, "sports"); dojo.publish("sports news", [["China will rank first in the 29th Olympic"]]); handle2 = dojo.subscribe("sports news", NewsReporter, "sports"); dojo.subscribe("entertainment news", NewsReporter, "entertainment"); dojo.subscribe("mixed news", NewsReporter, "mixed"); /*then publish*/ dojo.publish("sports news", [["America will rank second in the 29th Olympic", "Russia will third forth in the 29th Olympic"]]); dojo.publish("entertainment news", [["Red Cliff earns over 200 million in its first week"]]); dojo.publish("mixed news", [["Yao Ming gives Red Cliff high comments"], ["Jay and S.H.E wish Beijing Olympic success"]]); //unsubscribe two sports news reporter dojo.unsubscribe(handle1); dojo.unsubscribe(handle2); dojo.publish("sports news", [["this news has no consumer!"]]); </script> </body> </html>
在清单 7 的例子中,模拟了一个“新闻记者”(NewsReporter 对象),专门跑体育和娱乐新闻,任何此类新闻他都不会放过。 Dojo 就像一个新闻中心,发布各类新闻。
记者先在新闻中心注册,说自己对体育新闻感兴趣,接着新闻中心发布了一条新闻“ China will rank first in the 29th Olympic ”,这时新闻记者将立即收到这条消息,并报道出来(在本例中就是在浏览器的模拟控制台输出这条新闻)。然后记者又再次向新闻中心注册对体育和娱乐新闻以及跨这两个领域的新闻都感兴趣,然后新闻中心分别发布了这三个主题的新闻。记者当然不敢懈怠又马上输出了这些新闻,最后新闻记者不打算再跑体育新闻了,就在新闻中心取消了对体育新闻的注册。这个例子最终将在浏览器的模拟控制台输出:
sports:China will rank first in the 29th Olympic sports:America will rank second in the 29th Olympic sports:Russia will third forth in the 29th Olympic sports:America will rank second in the 29th Olympic sports:Russia will third forth in the 29th Olympic entertainment:Red Cliff earns over 200 million in its first week mixed sports: Yao Ming gives Red Cliff high comments entertainment: Jay and S.H.E wish Beijing Olympic success
从这个例子中我们可以得到几个使用订阅/发布模式时的注意事项。
- 先订阅,再发布。主题发布的时候,订阅了这一主题的事件处理函数会被立即调用。
- 发布函数的参数为数组,发布第一条新闻时使用的是
[["China will rank first in the 29th Olympic"]],这是一个二维数组,因为事件处理函数 NewsReporter.sports,NewsReporter.entertainment,以及 NewsReporter.mixed 的参数已经是一个数组,所以在发布时必须把新闻事件这个数组再放在另一个数组中才能传递给这些事件处理函数。而“ mixed ”新闻的处理函数有两个参数,所以发布“ mixed ”的新闻时,参数为:
[["Yao Ming gives Red Cliff high comments"], ["Jay and S.H.E wish Beijing Olympic success"]]
二维数组中的第一个数组表示体育新闻,第二个数组表示娱乐新闻。
- 取消订阅时,必须把所有的订阅都取消。重复的订阅行为返回的句柄是不一样的,在本例中 handle1 和 handle2 是不同的,必须都注销。只有在 handle1 和 handle2 都被注销后,新闻中心发布的体育新闻才不会被这个记者接收到。