Event 类: 事件对象,在事件监听中的回调函数中传递的参数e就是这个类的实例。
如果想在事件中传递参数,则应该写一个子类继承Event,然后把传递的参数作为属性添加到event上。
这个类也有静态方法。
源码解析:
继承Disposable。有三个属性:type,target,currentTarget.
1,构造函数Event(type, opt_target):传入两个参数:type,事件类型,一个字符串; target,抛出事件的实例,就是绑定了listener的实例,target被保存在this.target中,同时this.currentTarget = this.target。
注:target和currentTarget的区别,target是触发事件的源,而currentTarget是捕获事件的对象。例如:a包含在b中,在b上注册了事件监听,点击a后,冒泡到b,则target=a,而currenttarget=b。
2,returnValue_ 和 propagationStopped_两个属性,分别代表了 是否阻止默认事件 和是否阻止事件传播,returnValue_默认为true,propagationStopped_默认为false,也就是默认情况下不阻止默认事件,也不阻止事件传播。
3,对应的有preventDefault和stopPropagation方法来设置上述的两个值,以代表阻止默认事件或者阻止事件传播。
4,类Event有两个相同的静态方法 preventDefault和stopPropagation。实现和上述两个实例方法一样的效果。
EventType:一个常量,定义了默认的事件类型。包括:鼠标,键盘,表单,music,复制粘贴,html5等事件。
EventWrapper:接口,包含listen和unlisten两个方法。
BrowserEvent:继承自Event,除type,target,currentTarget之外,还包含了大量相关的属性:坐标,键盘状态,鼠标状态等。
Listener: 保存一个Listener的信息,注意,这个Listener和listen中传入的listener的区别,这个是对象,那个是函数,并且是包含关系:
listener: 回调函数,这个就是listen中传入的listener函数,别和Listener类的实例搞混了。
proxy:事件代理,用来捕获浏览器触发的事件,并触发对应listener。比如注册了a的click事件,则proxy捕获click事件,并调用注册的回调函数。
src:
type
capture
handler:这个就是回调函数中的this,如果未定义,则用src
callOnce
key:一个自增的id
handleEvent(e): 处理事件的函数,如果listener是函数,则调用listener.call(this,e), 否则调用listener.handleEvent(this, e);
EventWrapper:一个接口,只声明了listen和unlisten函数,没有具体实现。
这个接口意义是什么?可以在listen和unlisten中定义自己的行为?
EventTarget:所有事件源的父类,实现了这个接口的类可以有事件源的特征:注册监听,触发事件。因此,如果一个组件可以在其上添加事件监听,并且其可以触发事件,则其应该继承eventTarget。
一般来讲,一个EventTarget类应该有一个对应的EventType属性,用来定义此类所有的事件类型。
注意不用的时候,随时销毁listener,因为goog.events保存有所有listener的引用,所以不会被垃圾回收器自动回收。
继承子Disposable.
customEvent_ = true;表示这是一个用户定义的event target,区别于domevent,在listen()时不会调用addEventListener来添加对浏览器api的代理。
parentEventTarget_/getter/setter:
addEventListener/removeEventListener: 调用goog.events.listen/unlisten(this,xxxxx);唯一的区别就是把src指向了自己
dispatchEvent:调用goog.event.dispatchEvent(this,e);同上,唯一的区别是把src指向自己
一个实例对象(不是一个类),提供了事件处理的全部逻辑,从注册监听,到抛出事件,到捕获事件并触发回调函数。通过一个map来分类存储listener,通过listener.ky来唯一标示一个listener。如果重复注册(src,type,listener,capt都相同),则只返回listener.key。注意dom事件和custom事件的区别。如果是dom事件,其抛出事件是浏览器做的,而不是通过dispatch做的。对于dom事件,listen函数中会同时生成一个proxy来代理。
重要方法说明:
goog.events.listen(src,type,listener, opt_capt, opt_handler):
参数说明:
src:事件源,应该是个实现了EventTarget接口的实例,也可以是一个dom元素(dom元素本身就是EventTarget类型的,也有自己的dispatchEvent方法,只是各个浏览器的实现标准不同)。
type:事件类型,一个字符串,为了跨浏览器,最好使用goog.events.EventType中定义的事件类型,或者各个组件中定义好的事件类型。
listener:可以是一个函数或者是一个EventHandler实例,如果是一个函数,则触发事件时会调用这个函数。
其中的this:
1,如果src是一个EventHandler,则触发事件时调用eventhandler.handlerEvent方法,this就是event handler。
2,否则,this值就是opt_handler,
3,如果opt_handler也未定义,则this是src。
opt_capt:是否在捕获阶段触发事件,默认false,即默认在冒泡阶段触发事件
opt_handler:回调函数中的this,如果回调函数是一个EventHandler对象 则不需要此参数。
此方法用于绑定事件监听,最常用的方法。
源码解析:
listeners_:根据listener.key来分别存储每一个listener。这样可以根据key快速查找到listener。
listenersTree_: 可以看做一个三层tree,第一层保存了各种type,第二层,每个type下根据cap来分为true和false两个分支,第三层,根据srcId,最后分支为叶节点,叶节点下面保存的是注册到这个src下的所有listeners数组。
其结构如下:listenersTree_[eventtype][capt][srcid] ,下面的每一项是一个listenerArray,里面是一个个的listener。
sources_:根据listener.srcId来存储每一个listener
listen(src,type,listener, opt_capt, opt_handler):重点!注册事件监听。将一个listener添加到其在listenerTree_中对应的位置,如果路径不存在 则创建,如果重复添加,则返回listener.key。同时会注册一个proxy,这个proxy会调用对应的listener。所以,如果对一个<a>注册了一个监听,回调函数是fun,则点击此标签,并不是直接调用f,而是调用了f对应的proxy,proxy做一定的处理之后再调用fun,好处是在proxy中可以做一定的处理而屏蔽浏览器之间的差异。handler就是回调函数中的this。
参数说明:其中src是事件源,type是事件类型,listener是EventHandler类的实例或者是函数,opt_capt是 是否在捕获阶段触发。opt_handler:如果listener是函数,则handler是函数中的this,如果listener是一个EventHandler实例,则handler没有作用,因为回调函数中是listener.handleEvent,其this被指定为listener自身(this.listener.handleEvent.call(this.listener, e)。
listenOnce():调用listen,只是将listener.listenOnce = true;
[un]listenWidthWrapper();调用wrapper.[un]listen,参见wrapper中问题:有什么意义?
unlisten():在listenersTree_中找到listener,然后调用unlistenByKey(lietener.key);
unlistenByKey():根据key,在listeners_,listenersTree_和sources_中分别将此listener移除。在listenerTree_中移除是通过调用cleanUp_来完成的
cleanUp_清理一个listener数组。如果清理完后为空,则从listenerTree_删除此数组。
removeAll(opt_obj,opt_type,opt_capt):移除obj上的所有listener,如果obj!=null,则在sources_中根据srcId找到listeners,否则,在sources_中通过匹配type和capt来找到listeners,最终都是调用unlistenByKey来完成。
getListeners -> getListeners_(obj,type,capt),根据三个参数在listenersTree_中找出listeners。listenersTree_叶节点本来存在就是满足type&cap&src的listeners数组。
getListener(src,type,listener,opt_cap,opt_handler):同理,在listenersTree_中找到符合的Listener并返回。
hasListener(obj,opt_type,opt_cap):
expose:
fireListeners(obj,type,cap,eventObject):触发符合前三个参数的event,并且将eventObject当做参数传给回调函数。实现:在listenerTree_中找到listeners数组,然后逐个调用fireListener。
fireListener(listener, eventObject):直接调用listener.handleEvent(obj),达到的效果就是触发事件,并将obj作为参数传递给回调函数。
dispatchEvent(src, e):重点!触发一个事件。在src上触发一个事件,此函数主要做了三件事:
1,包装e,如果e是一个字符串,则创建一个Event(e, src),如果e是一个object但是不是Event类型,则创建一个Event继承e,否则,只设置e.target=e.target || src。
2,处理捕获阶段:从src向上查找其parent,并push到ancestors中,然后从length-1 -> 0诸葛触发parent上的对应事件。并将e.currentTarget = parent. e.target 总是固定的。
3,处理冒泡阶段:同捕获阶段,不同的是顺序相反:从0 -> length-1逐个触发parent上的对应事件。
捕获阶段和冒泡阶段触发事件都是通过fireListener_来完成的。
handleBrowerEvent_:捕获浏览器触发的事件,并调用对应的listener。proxy就是通过此函数来实现其功能的。因为proxy调用了proxyCallbackFunction,而在events.js中将proxyCallbackfunction = handleBrowerEvent。前面已经说过,注册监听的时候,如果不是customEvent,则同时生成一个proxy并将其注册给浏览器事件,这个proxy调用了handleBrowerEvent,handleBrowerEvent对event进行包装,生成一个browerEvent对象,并通过fireListener调用对应的listener并将browerEvent传递给回调函数。
InputHandler实际上是通过包装原有事件生成新的事件类型,这是一个通用的实现思路:
inputhandler实际上是以原有的事件为基础,通过包装生成自己的事件。主要包括如下两步:
1,在构造函数中传入被监听的对象element,然后监听可能会改变element的内容的事件:oninput || [keydown,paste,cut,drop](因为onpinut是新的标准,如果浏览器不支持,就通过keydown等事件来模拟),回调函数为this。因为如果回调函数不是函数类型会自动调用其handleEvent方法。
91 this.eventHandler_.listen(
92 this.element_,
93 this.inputEventEmulation_ ? ['keydown', 'paste', 'cut', 'drop'] : 'input',
94 this);
2,之后,当上述事件发生后,会调用this.handleEvent函数。在其中进行如下处理:
如果根据e的类型过滤,如果此event没有改变内容,比如按下ctrl键,则直接return。否则,表明内容已经被上述事件改变,则把生成一个自己的event对象(其实就是e.target = this.element_;e.type = 自己定义的type类型)。并通过dispatch方法在this.element_上抛出这个事件。
所以,InputHandler有三个重要属性/方法:
构造函数 InputHandler(element):传入被监听的dom对象
eventtype:自定义的事件类型
handEvent:事件的代理,包装一个新的事件并抛出