dojo的事件机制

使用 Dojo 处理 DOM 事件

当 Dojo 运行在支持 DOM Level2 事件模型的浏览器中时,Dojo 只是把事件处理委托给浏览器来完成。而在与 DOM Level2 的事件模型不兼容的浏览器(比如 IE)中,Dojo 会尽量使用浏览器的 API 模拟 DOM Level2 中的事件处理函数。 Dojo 最终提供给开发者一个称为“简单连接”的事件处理机制来处理 DOM 事件。

为什么叫“简单连接”呢,因为绑定事件处理函数的函数名叫 dojo.connect,相应的注销的函数是 dojo.disconnect 。

  • dojo.connect = function(/*Object|null*/ obj, /*String*/ event, /*Object|null*/ context, /*String|Function*/ method, /*Boolean*/ dontFix)
  • 参数 obj 事件源对象,比如 DOM 树中的某一节点;
  • event 参数表示要连接的事件名,如果是 dojo.global(在浏览器中一般是 window 对象)域上的事件,则 obj 参数可以置为 null,或者不写。
  • context 指事件处理函数所在的域(比如一个对象);
  • method 表示事件处理函数名,如果是全局函数,则 context 参数可置为 null,或者不写这个参数;
  • dontFix 表示不需要处理浏览器兼容的问题,默认为 false ;
  • 如果你的应用只在支持 DOM Level2 事件模型的浏览器上运行,则可以把它设为 true,但是这种几率太小了,因为 IE 就不是完全支持 DOM Level2 事件模型。 dojo.connect 函数可以返回一个 handle,在 dojo.disconnect 中会用到。
  • dojo.disconnect = function(/*Handle*/ handle)

    dojo.disconnect 函数用来注销已注册的事件处理函数,参数是一个 dojo.connect 时返回的 handle 。

下面来看看如何使用 Dojo 的简单连接机制处理 DOM 事件。


清单 5

				
 <html> 
 <head> 
 <script type="text/javascript" 
 djConfig="parseOnLoad: true, isDebug: true" 
 src="../dojo/dojo/dojo.js"></script> 
 </head><body><script> 
 function $(id) { return document.getElementById(id); } 
 function handler(eventObj) { 
	 console.info("eventType=" + eventObj.type + "; node=" 
			 + eventObj.target.id + "; currentTarget=" 
			 + eventObj.currentTarget.id); 
	 //if Shift Key pressed 
	 if (eventObj.shiftKey) { 
		 //stop bubbling 
		 eventObj.stopPropagation(); 
	 } 
 } 
 function handler2(eventObj) { console.info("this is for test"); } 
 function connect() { 
	 dojo.connect($("book"), "click" , handler); 
	 dojo.connect($("cpp"), "click" , handler); 
	 dojo.connect($("b1"), "mouseover" , handler); 
	 dojo.connect($("b1"), "mousedown" , handler); 
	 dojo.connect($("b1"), "click" , handler); 
	 dojo.connect($("b2"), "click" , handler); 
	 dojo.connect($("b2"), "click" , handler2); 
	 dojo.connect($("b3"), "click" , handler); 
 } 
 dojo.addOnLoad(connect); 
 </script> 
 <div id="book"> 
	 <ol id="cpp"> 
		 <li id="b1">C++ primer</li> 
		 <li id="b2">Thinking in C++</li> 
		 <li id="b3">Inside C++ object model</li> 
	 </ol> 
 </div> 
 </body> 
 </html>

 

清单 5 的页面中有一个跟 c++ 相关的书的列表,列表的每一项都通过 dojo.connect 绑定了一个或多个事件处理函数。第一项“ c++ primer ”给 mouseover,mousedown,click 三个事件注册了事件处理函数,第二项“ Thinking in c++ ”注册了两个 click 事件处理函数 handler 和 handler2,第三项“ Inside C++ object model ”绑定了 click 事件。这个例子可以说包括了“简单连接”机制的方方面面。

首先来看看 dojo.connect 的使用,dojo.connect 能够把多个事件处理函数绑定在一个事件上,第二项“ Thinking in c++ ”的 click 事件就绑定了两个事件处理函数。在本例中,并没有给 dojo.connect 函数传递事件处理函数的 context,因为默认的是 dojo.global,而两个事件处理函数 hander 和 hander2 都是全局函数,所以不需要显示传递 dojo.global 。

再来看事件处理函数 handler 和 handler2 。 handler2 只是用来说明 dojo.connect 可以绑定多个事件处理函数,不多说; handler 是主要的事件处理函数,在 handler 里先输出了事件对象的三个属性,type、target、currentTarget,type 表示事件的类型,target 表示事件目标节点,currentTarget 表示当前事件传递到哪个节点了,输出他们三个是为了说明 Dojo 也是在冒泡阶段处理事件的(还记得在 DOM 事件模型部分对事件的三个阶段的描述吗?)。所以当点击第三项 b3 时,在浏览器的模拟控制台输出是

 eventType=click; node=b3; currentTarget=b3 
 eventType=click; node=b3; currentTarget=cpp 
 eventType=click; node=b3; currentTarget=book

 

可以看出,首先会触发 b3 的事件处理函数,然后是 id 为 cpp 的 ol 元素的 click 事件处理函数,最后是 id 为 book 的 div 。所以毫无疑问 Dojo 是在事件的冒泡阶段处理事件的,capture 阶段并不做任何处理。 handler 的最后是关于阻止事件传播的代码,如果按住 shift 键,再点击第三项时,只会在模拟控制台输出:

eventType=click; node=b3; currentTarget=b3

 

后面两个事件没有发生,因为 click 事件被 stopPropagation 阻止了,没有再往上冒。事实上,可以在任何一级对象上调用 stopPropagation 阻止事件继续往上传递。

然后是事件对象 eventObj,事件对象是对事件的描述,在前面已经介绍了事件对象的几个有用的属性。 Dojo 的事件对象其实基于 DOM Level2 的事件对象,更详细的属性信息可以参考 Dojo 的官方文档,这里对用户操作触发的事件和事件的继承结构做些说明。当用户点击第一项 b1 时,在浏览器输出的是

eventType=mouseover; node=b1; currentTarget=b1
eventType=mousedown; node=b1; currentTarget=b1
eventType=click; node=b1; currentTarget=b1
eventType=click; node=b1; currentTarget=cpp 
eventType=click; node=b1; currentTarget=book

 

从上面的输出可以看出,在 b1 这个节点上一共监测到了三个事件(事实上产生的事件不止三个),mouseover、 mousedown、click 。所以表面上一个点击操作背后却藏着大文章。同理用户点击提交按钮提交一个表单也会触发很多事件,但一般我们只处理了最上层的 submit 事件。这些现象揭示了事件是有类别,层次的,底层事件可以触发高层事件。底层事件一般都是与设备有关的事件,比如鼠标移动,按键产生的事件;高层事件一般指页面元素上的事件,比如链接的 click 事件,表单的 submit 事件等。与设备无关的事件往往由几个与设备有关的事件触发。比如一个单击页面上按钮的 click 事件,可以分解为 mouseover, mousedown, mouseup 三个事件,在这三个事件发生之后,将触发按钮的 click 事件。开发人员应该了解这些知识,因为它有助于写出高效的事件处理程序。

最后是事件目标,在 W3C DOM Level2 的事件模型里,事件目标不仅仅是 DOM 树种最底层的接收事件的节点,它可以是从这个底节点到跟节点路径上的任何一个节点。

Dojo 目前支持的事件类别包括 UIEvent,HTMLEvent, MouseEvent,每类事件具有的属性并不一样,比如只能在 MouseEvent 里才能获得事件发生时鼠标的位置等。

使用 Dojo 处理用户自定义事件

既然 W3C 已经定义了标准的 DOM Level2 事件模型,为什么 Dojo 还要提供 connect 函数来注册事件处理函数呢,为何不使用 DOM Level2 的 addEventListener 函数?从前面的叙述中也看不出 connect 与 addEventListener 有明显的不同之处。确实在处理 DOM 事件上,Dojo 的 connect 与 addEventListener 无甚大的不同,但是 Dojo 的 connect 函数还可以处理用户自定义事件。这是 addEventListener 所不具备的。下面来看看怎么使用 dojo.connect 来处理用户自定义事件。

用户自定义事件是指用户指定的函数被调用时触发的“事件”,当指定函数被调用时,将促发监听函数被调用。有点类似于 AOP 的编程思想,但在 Javascript 中实现 AOP 比起面向对象的编程语言要简单得多。


清单 6

				
 <html> 
 <head> 
 <script type="text/javascript" 
 djConfig="parseOnLoad: true, isDebug: true" 
 src="../dojo/dojo/dojo.js"></script> 
 </head> 
 <body> 
 <script type="text/javascript"> 
 function print(fName, args) { 
	 var message = "In " + fName + "; the arguments are: " 
	 dojo.forEach(args, function(args) { 
		 message += args.toString() + " "; 
	 }) ; 
	 console.log(message); 
 } 
 function handler1() { print("handler1", arguments); } 
 function handler2(a1, a2) { print("handler2", [a1, a2]); } 
 function userFunction() { 
	 print("userFunction", arguments); 
 } 
 dojo.connect("userFunction", null, "handler1"); 
 dojo.connect("userFunction", null, "handler2"); 
 userFunction(1, 2); 
 </script> 
 </body> 
 </html>

 

运行清单 6 的例子,会在页面中的一个模拟控制台中输出:

In userFunction; the arguments are: 1 2 
 In handler1; the arguments are: 1 2 
 In handler2; the arguments are: 1 2

 

调用 userFunction 时,handler1 和 handler2 也被触发了。 userFunction 就像是一个事件源,它的调用像一个事件,而 handler1 和 hander2 就是事件处理函数。那么这种情况下,事件对象又在哪呢? handler1 事件处理函数没有显式的参数,通过在控制台的输出可以得知它实际上有两个参数,值分别为 1 和 2 ; handler2 有两个显式参数,值也为 1 和 2 。所以 Dojo 只是把 userFunction 的两个参数传递给了事件处理函数,不像在处理 DOM 事件时,提供一个封装好的事件对象。在本例中 userFunction 只“连接”了两个函数,很显然它还可以连接更多的事件处理函数,这些事件将按连接的先后顺序来执行。

Dojo 的订阅/发布模式

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

 

从这个例子中我们可以得到几个使用订阅/发布模式时的注意事项。

  1. 先订阅,再发布。主题发布的时候,订阅了这一主题的事件处理函数会被立即调用。
  2. 发布函数的参数为数组,发布第一条新闻时使用的是

    [["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"]]

    二维数组中的第一个数组表示体育新闻,第二个数组表示娱乐新闻。

  3. 取消订阅时,必须把所有的订阅都取消。重复的订阅行为返回的句柄是不一样的,在本例中 handle1 和 handle2 是不同的,必须都注销。只有在 handle1 和 handle2 都被注销后,新闻中心发布的体育新闻才不会被这个记者接收到。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值