KISSY事件模型研究(1)

最近做到一个需求,是基于KISSY完成核心代码的构建,对其中的事件模型很是有好感,借今日有空闲时间,正好研究研究,做了一下记录,供大家学习参考,有误之处还望大家及时指出!

首先让我们想一想,如果要给一个div元素添加一个click事件处理函数,常用的方法有两种吧:
一种:

div.addEventListener('click', foo, false);

或者:

div.onclick = foo;

这两种传统的事件注册方法,都涉及到三个要素,第一,要注册事件的元素target - div;第二,要注册的事件类型type - click;第三,要注册的事件处理函数fn - foo。这三个便是一个事件注册中需要关注的三个主要方面,我们称之为事件三要素。

那么我们就从刚刚所说的事件三要素中,考虑kissy是如何给事件目标对象注册事件的。 不过这里我还要啰嗦一句,我们这里所说的事件目标对象是在kissy中的一个特殊对象,除text/comment节点元素之外的所有DOM元素都是事件目标对象,当然,我们也可以有自己的事件目标对象,但它必须特殊处理才能成为事件目标对象,这个我们可以稍候再说。

1. 事件目标对象:这个即事件三要素中的target,它是整个事件注册过程中的主体,没有了它,一切无从谈起,因此我们首先要判断它是否是一个合法的事件目标对象,这里的要求很简单,只要是非text/comment节点元素的一切对象,都可以充当事件目标对象(不过就像我之前试过的,我们的自定义对象需要做一些特殊处理)。有了这个事件目标对象,我们要怎么处理它呢?自然是注册指定类型的事件处理函数,但是这里变遇到了个问题,我依然要像从前一样把事件处理函数直接注册到这个事件目标对象吗?大家都知道直接注册上去的弊端,其一就是无法统一管理,其二就是容易导致内存泄漏。对于这样两个严重的弊端,我们当然不能视为不久,因此这里我要隆重像大家介绍一个概念——事件缓存。

这里的事件缓存其实应该称为事件仓库更为且当,因为其中存放了所有使用我们特定事件注册方法注册的事件处理函数。正如刚刚所说的一样,那为什么会有事件仓库这个概念呢,首先是考虑到对事件的统一管理上,所有我们添加过的事件都能在仓库里面找到,而不像从前一样,事件都是独立分开,无法统一管理的,这里的管理,主要指的是对事件的添加、删除和处理;其二,事件仓库能够帮助我们减少内存泄漏的危险,从此,我们的事件不是直接注册到一个元素上,而是通过一个钩子,连接元素和事件仓库,所有的事件都存放在这儿,自然不会产生内存泄漏。对于刚刚说到的这个钩子,就是我们所说的事件目标对象的事件id,这个唯一的id标识于事件目标对象上,则可在事件目标对象和事件仓库中建立唯一连接。

这里设计的事件仓库数据模型如下图所示:

event_cache

这里对于事件仓库的组织结构很清晰,每个唯一事件id对应于唯一的事件目标对象,target即事件目标对象,events则存放该事件目标对象所有类型的事件,对于其中一个类型的事件,包括一个处理函数即事件代理和数个侦听器,每个侦听器中存放用户注册的事件处理函数和作用域。

既然已经了解了事件缓存的概念,那么需要在事件缓存和事件目标对象建立,就是刚刚提到的那个钩子,我们的事件注册函数自然会帮我们初始化并维护一个这些唯一的id用于建立事件缓存和事件目标对象的连接。则从此,我们已经把事件目标对象存放到事件缓存中,到此,事件目标对象的处理也算基本完成了。

2. 事件类型:下一个我们要考虑的要素就是我们要注册的事件类型。对于事件目标对象,在其上可发生诸多事件,我们就需要在上面注册指定类型的事件,然后当该事件发生时用相应的处理函数去处理,这个概念相信在DOM节点上大家都很清楚,正如文章开头给出的例子,我们就是在div这个事件目标对象注册了click这个事件类型的事件处理函数foo。那么这个事件类型怎么和事件仓库连接起来呢?再回过头去看那个数据模型图,那个events对象正是用来存储我们注册到事件目标对象的所有事件的对象。如果我们添加到其中的某一类型事件还不存在,就需要对该类型的事件进行初始化,包括建立一个统一事件代理,初始化侦听器数组。到此,我们的事件注册函数,对于事件类型的处理部分就完成了。

3. 事件处理函数:这一要素又是整个事件注册的核心要素。这里我就需要考虑到我们的自定义事件目标对象和传统的事件目标对象。传统的事件目标对象一般指DOM节点元素,这些对象有原生的事件注册方法(这里要指出的是kissy里面还有一个Node类型的DOM节点元素包装,其本质还是DOM节点元素),我们需要为他们注册侦听器,用我们统一的事件代理进行处理;而我们的自定义事件目标对象则不需要单独的事件注册,因为这些自定义事件都是需要“手动”触发的,因此不需单独的监听器绑定。最后已经往事件目标对象注册了侦听器,交由统一的事件代理,那么我们还需把我们自己需要绑定的事件处理函数,压入到我们的事件侦听器数组listeners里面去。这里说一下基本的事件处理思路,对于传统的事件目标对象,我们都往同一种类型是事件上注册唯一一个统一的事件代理,然后事件触发后有其统一处理,而我们自己的事件处理函数呢,存放于events对象的侦听器数组listeners里,在事件触发时,我们可以在事件代理再触发每个侦听器函数,这样就完整地模拟了传统事件目标对象的事件触发-处理;而对于我们的自定义事件目标对象,在手动调用fire方法触发事件时,就可以触发每个事件侦听器函数。

到此,一个完整的事件注册过程变完成了。其他事件模型的相关函数之后再介绍哈~

- EOF -

11 Comments For This Post

  1. :surprised:
    初中开始博客,我喜欢,呵呵

    回复该留言

  2. 分析得不错。

    我有个疑问,事件发生时,KISSY每次都会New一个EventObject,把原事件对象的有关属性拷贝过去,并进行fix处理,这样会不会影响效率?有相关的测试吗?

    回复该留言

    @hi, 对这个问题问得很好哈,光从代码上看(当然这里主要还是考虑dom事件,自定义事件影响不大),性能的主要消耗是在fix上,的确是需要进行测试,分析影响程度。我想如果涉及到的性能问题就应该是大规模触发的事件,比如mousemove之类的,会产生大量事件的注册,如果是普通事件,性能影响肯定不是什么问题。

    最后再次感谢这个问题哈,下篇文章就好好的介绍介绍EventObject这个东东,然后想办法做一个科学的性能测试 :biggrin:

    @ghSky, fix之前的new EventObjet和属性拷贝的消耗相比fix很小? 我也是考虑类似mousemove频发触发的dom事件下的性能问题。 期待你的测试和分析。 也可以对比其他框架下event对象的处理进行分析。

  3. 学习了!博主能告诉你这类图是用什么软件画的吗?

    回复该留言

    @燃烧de2B, 是用XMind画的哦~

  4. 这也能拿出来吹嘘?很多现有库里都有时间缓存的机制吧?我们公司自有的JS库里早就有这种机制了。哎,taobao的技术人员多关注关注技术吧,别拿这种过时的东西再来吹嘘,骗人了。

    回复该留言

    @kris, “这也能拿出来吹嘘?很多现有库里都有时间缓存的机制吧?我们公司自有的JS库里早就有这种机制了。哎,taobao的技术人员多关注关注技术吧,别拿这种过时的东西再来吹嘘,骗人了。”这话说得有点欠妥吧,照你这么说,小学,中学的也就没有必要了。楼主关于kissy的这几篇文章真的不错,学了不少东西,顶楼主一个,希望楼主继续。

    @kris, 光说别人什么不好,那得了,您也把Jquery的事件机制介绍介绍啊,好让新人学习学习啊,期待中...

    @kris, 多值得骄傲啊,你们公司早有了,LZ的侧重点是在说他们好不容易有这个机制了吗

  5. 你娃blog访问量真大口水中。。。
    事件仓库这种东西应该是各个for前端的js框架都有的东西吧
    例如jquery的live也是放到jQuery.data里,感觉更有逻辑性一些
    具体的实现过程就如你所说的
    除开性能问题,我更关系使用的时候是否优雅
    例如jquery的美元符号里面里放的是css选择字符,然后通过live绑定事件的话,就十分靠谱
    ecma的prototype继承对dom的事件绑定之类似乎没什么关系,于是jquery的live绑定十分聪明的一点就是把css中的class看成是一个实体html对象的parent,简单直接
    kissy也是借鉴了jquery了这种做法吧
    不过我更希望什么时候浏览器们,可以通过css选择字符来预先定义事件绑定,然后由浏览器们原生地处理这些继承关系,就像css风格一样自动绑定事件代码XD

    回复该留言

    @aligo, 呵呵,你这娃儿也来咯~~流量还是拜大牛所赐嘛,嘻嘻~

    你的这种理解的确很精妙哈,HTML对象就是class的实例,这时候class就是真正的“类”咯,而HTML对象就成了这个类的实例,这样的解释的确精妙啊!肯恩John Resig大牛的初衷就是这个吧!

    嗯,你说得的确很对,js语言本身和浏览器的结合的确没有到更深的一个层次,所以写HTML中的js远没有js自身语言写得那么轻巧,行云流水~可能这正是前端类库的一个目的吧,让两者更彻底的结合~

    其实我之前很少接触类库,都是用原生方法开发,接触了jQuery后感觉真的和原生方法完全不同,jQ可以写得如期轻巧精妙,而原生的确很臃肿~但是换个角度想想,js这门语言,除去一些糟粕,也是十分小巧精悍的语言,prototype继承完全摒弃传统class-instance的继承思想,真正实现了适合js本身的oo方式,这是一种突破~

    你最后说的优雅写法可能是我之前没有深刻体会的吧,但KISSY的愿景恰好就是An Enjoyable UI Library,这正是需要探索和实践的,不是单纯学习、模仿能实现的!jQuery的链式风格正是一种勇敢的尝试,并且得到了意想不到的效果,可完全用这种风格能不能enjoyable呢?这正是我们在探索中能够逐步回答 :biggrin:

    最后再谢谢娃儿哈~哈哈!

  6. 大致看了一下kissy的一些东西。 里面写法有点冗余

    回复该留言

    @sasumi, 能举几个例子不?

  7. 事件绑定无非就是那几种,一种就是放事件处理在一个数组里面,然后对这个数组进行统一的注册(或者直接占用对象整个事件),另外一种就是使用原生的事件添加,例如addEventListener。
    第一种的最大最不喜欢的就是完全占用了原有的事件调动机制。例如绑了一个事件之后,就不能在使用 dom.click = function(){}这种模式了。

    回复该留言

  8. 从 ued.taobao.com 过来瞧瞧,杠杠的!

    回复该留言

  9. 哈,有3个方法吧。。直接注册在标签上呢。。。

    回复该留言

    @xiaomifeng, 确实,这个疏忽了哈~

  10. - -!从taobao UED转过来看看。。貌似到淘宝实习了?

    回复该留言

    @hbczxjq, 额,是啊~~你最近可好啊~

2 Trackbacks For This Post

  1. [...] 浩行天下 据说从初中就开始写博文,所以文章质量那是杠杠的:KISSY事件模型研究(1). 有志于深入了解 KISSY [...]

  2. [...] 3.事件管理的东西,ghSky同学写过三篇文章,讲得很细致。1 2 3 [...]

Leave a Reply

:wink: :twisted: :surprised: :smile: :sad: :rolleyes: :redface: :razz: :question: :neutral: :mrgreen: :mad: :lol: :idea: :exclaim: :evil: :eek: :cry: :cool: :confused: :biggrin: :arrow: