dojo事件驱动编程之事件绑定
什么是事件驱动?
事件驱动编程是以事件为第一驱动的编程模型,模块被动等待通知(notification),行为取决于外来的突发事件,是事件驱动的,符合事件驱动式编程(Event-Driven Programming,简称EDP)的模式。
何谓事件?通俗地说,它是已经发生的某种令人关注的事情。在软件中,它一般表现为一个程序的某些信息状态上的变化。基于事件驱动的系统一般提供两类的内建事件(built-in event):一类是底层事件(low-level event)或称原生事件(native event),在用户图形界面(GUI)系统中这类事件直接由鼠标、键盘等硬件设备触发;一类是语义事件(semantic event),一般代表用户的行为逻辑,是若干底层事件的组合。比如鼠标拖放(drag-and-drop)多表示移动被拖放的对象,由鼠标按下、鼠标移动和鼠标释放三个底层事件组成。
还有一类用户自定义事件(user-defined event)。它们可以是在原有的内建事件的基础上进行的包装,也可以是纯粹的虚拟事件(virtual event)。除此之外,编程者不但能定义事件,还能产生事件。虽然大部分事件是由外界激发的自然事件(natural event),但有时程序员需要主动激发一些事件,比如模拟用户鼠标点击或键盘输入等,这类事件被称为合成事件(synthetic event)。这些都进一步丰富完善了事件体系和事件机制,使得事件驱动式编程更具渗透性。
上图为一个典型的事件驱动式模型。事件处理器事先在关注的事件源上注册,后者不定期地发表事件对象,经过事件管理器的转化(translate)、合并(coalesce)、排队(enqueue)、分派(dispatch)等集中处理后,事件处理器接收到事件并对其进行相应处理。通过事件机制,事件源与事件处理器之间建立了松耦合的多对多关系:一个事件源可以有多个处理器,一个处理器可以监听多个事件源。再换个角度,把事件处理器视为服务方,事件源视为客户方,便是一个client-server模式。每个服务方与其客户方之间的会话(session)是异步的,即在处理完一个客户的请求后不必等待下一请求,随时可切换(switch)到对其他客户的服务。
在web环境中事件源由DOM充当,事件管理器对于web开发者来说是透明的,由浏览器内部管理,事件处理器便是我们绑定在dom事件中的回调函数。
Web事件处理流程
DOM2.0模型将事件处理流程分为三个阶段:一、事件捕获阶段,二、事件目标阶段,三、事件起泡阶段。如图:
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件起泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。
然而在此末法时代,浏览器两大派别对于事件方面的处理,常常让前端程序员大伤脑筋,所以任何前端库首先要对事件机制进行统一。
dojo中的事件绑定
dojo事件体系能够帮我们解决哪些问题?
- 解决浏览器兼容性问题:触发顺序、this关键字、规范化的事件对象(属性、方法)
- 可以在一个事件类型上添加多个事件处理函数,可以一次添加多个事件类型的事件处理函数
- 统一了事件的封装、绑定、执行、销毁机制
- 支持自定义事件
- 扩展组合事件
dojo中处理浏览器事件的代码位于dojo/on模块中,在官网中可以查看该函数的签名:
其中type可以是一个事件名称如:“click”
- require(["dojo/on", "dojo/_base/window"], function(on, win){
- var signal = on(win.doc, "click", function(){
- // remove listener after first event
- signal.remove();
- // do something else...
- });
- });
亦可以是由逗号分隔的多个事件名组成的字符串,如:"dblclick,click"
- require("dojo/on", function(on){
- on(element, "dblclick, touchend", function(e){
- // handle either event
- });
- });
亦可以是由冒号分隔"selector:eventType"格式进行事件委托使用的字符串,如:".myClass:click"
- require(["dojo/on", "dojo/_base/window", "dojo/query"], function(on, win){
- on(win.doc, ".myClass:click", clickHandler);
- });
亦可以是一个函数,如:touch.press、on.selector()
- require(["dojo/on", "dojo/mouse", "dojo/query!css2"], function(on, mouse){
- on(node, on.selector(".myClass", mouse.enter), myClassHoverHandler);
- });
查看一下on函数的源码
- var on = function(target, type, listener, dontFix){
- if(typeof target.on == "function" && typeof type != "function" && !target.nodeType){
- // delegate to the target's on() method, so it can handle it's own listening if it wants (unless it
- // is DOM node and we may be dealing with jQuery or Prototype's incompatible addition to the
- // Element prototype
- return target.on(type, listener);
- }
- // delegate to main listener code
- return on.parse(target, type, listener, addListener, dontFix, this);
- };

- 如果type是方法,则交给type自身去处理;比如touch.press 、on.selector
- 多事件的处理;事件可能是通过逗号键分隔的字符串,所以将其变成字符串数组
- 对于事件数组依次调用on.parse
- 添加事件监听器
- on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){
- if(type.call){
- // event handler function
- // on(node, touch.press, touchListener);
- return type.call(matchesTarget, target, listener);
- }
- if(type instanceof Array){
- // allow an array of event names (or event handler functions)
- events = type;
- }else if(type.indexOf(",") > -1){
- // we allow comma delimited event names, so you can register for multiple events at once
- var events = type.split(/\s*,\s*/);
- }
- if(events){
- var handles = [];
- var i = 0;
- var eventName;
- while(eventName = events[i++]){
- handles.push(on.parse(target, eventName, listener, addListener, dontFix, matchesTarget));
- }
- handles.remove = function(){
- for(var i = 0; i < handles.length; i++){
- handles[i].remove();
- }
- };
- return handles;
- }
- return addListener(target, type, listener, dontFix, matchesTarget);
- };
- 处理事件委托,dojo中事件委托的书写格式为:“selector:eventType”,直接交给on.selector处理
- 对与touchevent事件的处理,具体分析以后再说
- 对于stopImmediatePropagation的修正
- 支持addEventListener的浏览器,使用浏览器自带的接口进行处理
- 对于不支持addEventListener的浏览器进行进入fixAttach函数
- function addListener(target, type, listener, dontFix, matchesTarget){
- // event delegation:
- var selector = type.match(/(.*):(.*)/);
- // if we have a selector:event, the last one is interpreted as an event, and we use event delegation
- if(selector){
- type = selector[2];
- selector = selector[1];
- // create the extension event for selectors and directly call it
- return on.selector(selector, type).call(matchesTarget, target, listener);
- }
- // test to see if it a touch event right now, so we don't have to do it every time it fires
- if(has("touch")){
- if(touchEvents.test(type)){
- // touch event, fix it
- listener = fixTouchListener(listener);
- }
- if(!has("event-orientationchange") && (type == "orientationchange")){
- //"orientationchange" not supported <= Android 2.1,
- //but works through "resize" on window
- type = "resize";
- target = window;
- listener = fixTouchListener(listener);
- }
- }
- if(addStopImmediate){
- // add stopImmediatePropagation if it doesn't exist
- listener = addStopImmediate(listener);
- }
- // normal path, the target is |this|
- if(target.addEventListener){
- // the target has addEventListener, which should be used if available (might or might not be a node, non-nodes can implement this method as well)
- // check for capture conversions
- var capture = type in captures,
- adjustedType = capture ? captures[type] : type;
- target.addEventListener(adjustedType, listener, capture);
- // create and return the signal
- return {
- remove: function(){
- target.removeEventListener(adjustedType, listener, capture);
- }
- };
- }
- type = "on" + type;
- if(fixAttach && target.attachEvent){
- return fixAttach(target, type, listener);
- }
- throw new Error("Target must be an event emitter");
- }
对于上面的分析我们可以得出几个结论:
- 对于没有特殊EventType和普通事件都用addEventListener来添加事件了。
- 而特殊EventType,则用了另一种方式来添加事件(fixAttach)。
- 对于事件委托交给了on.selector处理
- target
- currentTarget
- relatedTarget
- stopPropagation
- preventDefault
- event的坐标位置兼容放到了dom-geometry的normalizeEvent中处理
- keycode与charcode的处理
调用on中传入的事件监听器,如果监听器中掉用过stopImmediatePropagation缓存lastEvent,供以后使用
- var fixAttach = function(target, type, listener){
- listener = fixListener(listener);
- if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top ||
- has("jscript") < 5.8) &&
- !has("config-_allow_leaks")){
- // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below.
- // Here we use global redirection to solve the memory leaks
- if(typeof _dojoIEListeners_ == "undefined"){
- _dojoIEListeners_ = [];
- }
- var emitter = target[type];
- if(!emitter || !emitter.listeners){
- var oldListener = emitter;
- emitter = Function('event', 'var callee = arguments.callee; for(var i = 0; i<callee.listeners.length; i++){var listener = _dojoIEListeners_[callee.listeners[i]]; if(listener){listener.call(this,event);}}');
- emitter.listeners = [];
- target[type] = emitter;
- emitter.global = this;
- if(oldListener){
- emitter.listeners.push(_dojoIEListeners_.push(oldListener) - 1);
- }
- }
- var handle;
- emitter.listeners.push(handle = (emitter.global._dojoIEListeners_.push(listener) - 1));
- return new IESignal(handle);
- }
- return aspect.after(target, type, listener, true);
- };
关于aspect.after的具体工作原理,请看我的这篇文章:Javascript事件机制兼容性解决方案
接下来我们看一下委托的处理:
为document绑定click事件,click事件出发后,判断event.target是否满足选择符“button.myclass”,若满足则执行clickHandler。为什么要判断event.target是否满足选择条件,document下可能有a、也可能有span,我们只需要将a的click委托给document,所以要判断是否满足选择条件。委托过程的处理主要有两个函数来解决:on.selector、on.matches.
- 处理matchesTarget在matches方法中使用
- 如果eventType含有bubble方法进行特殊处理
- 其他普通情况,为代理元素绑定事件回调
红框部分就是判断event.target是否匹配选择符,如果匹配则触发事件回调clickHandler.
- 获取有效的matchesTarget,matchesTarget是一个拥有matches方法的对象,默认取dojo.query
- 对textNode做处理
- 检查event.target的祖先元素是否满足匹配条件
- on.matches = function(node, selector, context, children, matchesTarget) {
- // summary:
- // Check if a node match the current selector within the constraint of a context
- // node: DOMNode
- // The node that originate the event
- // selector: String
- // The selector to check against
- // context: DOMNode
- // The context to search in.
- // children: Boolean
- // Indicates if children elements of the selector should be allowed. This defaults to
- // true
- // matchesTarget: Object|dojo/query?
- // An object with a property "matches" as a function. Default is dojo/query.
- // Matching DOMNodes will be done against this function
- // The function must return a Boolean.
- // It will have 3 arguments: "node", "selector" and "context"
- // True is expected if "node" is matching the current "selector" in the passed "context"
- // returns: DOMNode?
- // The matching node, if any. Else you get false
- // see if we have a valid matchesTarget or default to dojo/query
- matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query;
- children = children !== false;
- // there is a selector, so make sure it matches
- if(node.nodeType != 1){
- // text node will fail in native match selector
- node = node.parentNode;
- }
- while(!matchesTarget.matches(node, selector, context)){
- if(node == context || children === false || !(node = node.parentNode) || node.nodeType != 1){ // intentional assignment
- return false;
- }
- }
- return node;
- }
对比dojo与jquery的事件处理过程,可以发现jQuery在事件存储上更上一筹:
dojo直接绑定到dom元素上,jQuery并没有将事件处理函数直接绑定到DOM元素上,而是通过.data存储在缓存.cahce上。
声明绑定的时候:
- 首先为DOM元素分配一个唯一ID,绑定的事件存储在
.cahce[唯一ID][.expand ][ 'events' ]上,而events是个键-值映射对象,键就是事件类型,对应的值就是由事件处理函数组成的数组,最后在DOM元素上绑定(addEventListener/attachEvent)一个事件处理函数eventHandle,这个过程由 jQuery.event.add 实现。
执行绑定的时候:
- 当事件触发时eventHandle被执行,eventHandle再去$.cache中寻找曾经绑定的事件处理函数并执行,这个过程由 jQuery.event. trigger 和 jQuery.event.handle实现。
- 事件的销毁则由jQuery.event.remove 实现,remove对缓存$.cahce中存储的事件数组进行销毁,当缓存中的事件全部销毁时,调用removeEventListener/ detachEvent销毁绑定在DOM元素上的事件处理函数eventHandle。
以上就是dojo事件模块的主要内容,如果结合Javascript事件机制兼容性解决方案来看的话,更有助于理解dojo/on模块。
如果您觉得这篇文章对您有帮助,请不吝点击一下右下方的推荐,谢谢!
参考文章:
dojo事件驱动编程之事件绑定的更多相关文章
- Dojo初探之5:dojo的request(请求)操作、请求过程事件绑定和隐藏数据data()操作(基于dojo1.11.2版本)
前言: 上一章详细阐述了dojo的事件绑定操作,本章将讲解dojo的请求操作 注:dojo的请求操作与js和jquery完全不同! 1.dojo的请求 dojo通过request.get()/.put ...
- Dojo初探之4:dojo的event(鼠标/键盘)事件绑定操作(基于dojo1.11.2版本)
前言: 上一章详解了dojo的dom/query操作,本章基于dom/query基础上进行事件绑定操作 dojo的事件 dojo的事件绑定操作分为鼠标和键盘两种进行详解 1.鼠标事件 我们沿用上一章中 ...
- dojo事件绑定
Dojo如何动态绑定事件的小demo <html> <title>事件绑定测试test</title> <head></head> < ...
- python之Gui编程事件绑定 2014-4-8
place() 相对定位与绝对定位 相对定位 拖动会发生变化 绝对定位不会from Tkinter import *root = Tk()# Absolute positioningButton(ro ...
- angularjs学习之六(angularjs中directive指令的一般编程事件绑定 模板使用等)
angular js 中模板的使用.事件绑定以及指令与指令之间的交互 相应教学视频地址(需FQ):v=aG8VD0KvUw4">angularjs教学视频 <!doctype h ...
- 关于Web开发里并发、同步、异步以及事件驱动编程的相关技术
一.开篇语 我的上篇文章<关于如何提供Web服务端并发效率的异步编程技术>又成为了博客园里“编辑推荐”的文章,这是对我写博客很大的鼓励,也许是被推荐的原因很多童鞋在这篇文章里发表了评论,有 ...
- python2.0_s12_day9_事件驱动编程&异步IO
论事件驱动与异步IO 事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定.它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理.另外两种常见的编程范式是(单线程)同步以及 ...
- JS 中的事件绑定、事件监听、事件委托
事件绑定 要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数.所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称. 在JavaScript中,有 ...
- WPF Event 在 Command 中的应用初级篇,支持所有Event 展示松耦合设计的全部代码 - 解决TextBoxBase.TextChanged或者TextBox.TextChanged等类似事件绑定问题。
做过WPF开发的人,都知道做MVVM架构,最麻烦的是Event的绑定,因为Event是不能被绑定的,同时现有的条件下,命令是无法替代Event.而在开发过程中无法避免Event事件,这样MVVM的架构 ...
随机推荐
- flask-admin众博客概述
最近用flask admin(https://flask-admin.readthedocs.org/en/latest/)构建自动化发布平台,发现flask admin蛮强大的,基本上不需要自己写太 ...
- Linux_arm驱动之按键模拟脉冲实现定时器的精确计时
/***************************************************************** 内核驱动部分button_ker.c ************** ...
- android的单击监听事件
Button button = (Button) findViewById(R.id.button1); //1.直接new出来 button.setOnClickListener(new View. ...
- 使用内存虚拟硬盘 提高ArcGIS server并发性能的一种方法
1 问题提出 1.1 概述 提高ArcGIS server并发性能的方法很多,本文讨论在用户硬件足够强大的情况下(主要是内存足够大),使用内存模拟硬盘来提高数据的读取效率,以达到提高ArcGIS se ...
- LinkedHahsMap和HashMap的比较
http://www.cnblogs.com/hubingxu/archive/2012/02/21/2361281.html#commentform 一般情况下,我们用的最多的是HashMap,在M ...
- 【洛谷·P2320】鬼谷子的钱袋
这道题很神奇 我们举一个例子,m=12 那么我们可以把它分成两部分,L和R: (1,2,,6)(7,8,,12) 我们可以发现R中的数都可以由12/2和左边的数组合得到 那么我们对L再分------ ...
- java的三元运算符
1.三元运算符语法:判断表达式?表达式1:表达式2: (1)三元运算符适合于判断2个值到底使用哪一个! public static void mian(String[] args){ int sex= ...
- [fortify] preg_replace命令注入
慎用preg_replace危险的/e修饰符(一句话后门常用) 作者: 字体:[增加 减小] 类型:转载 时间:2013-06-19我要评论 要确保 replacement 构成一个合法的 PHP 代 ...
- HibernateUtil
package com.ssh.util; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.Standa ...
- 为什么一个类的全局变量默认以m开头?
某天闲着无聊,突然想起来为什么大家都习惯将全局变量使用m开头,于是追根求源,查了一些资料,虽然并不是我想要的,但是也总结一下. 在stackoverflow上就有人问: Why do most var ...