距离上一篇文章刚好一周的时间,一周上班的忙碌,真的回来之后都不想动手写点儿东东了~还是应该向承玉学习啊,博客能够保持那么高更新频率~话说这周博客访问突增,多亏玉伯的文章推荐哈~所以以后得再接再厉了!闲话不多说了,继续KISSY时间模型的研究~
话说上一篇文章后面得到了很多网友的回复和支持,其中一位网友提到了一个很直接的问题,不过我们暂时先不说这个问题,继续我们上次的话题。上次我们说到用KISSY的方法注册函数的时候,同一类型的DOM事件都是交由一个事件代理进行统一处理的,那么这次讨论点就从这个事件代理出发。
说到事件代理,相信对JavaScript有一定了解的人肯定都很熟习,说简单了,这个事件代理就是一个事件处理函数,我们在注册事件的时候,也是把它注册到事件目标对象上的。这里就涉及到事件的冒泡和捕获,感兴趣的同学可以先google了解一下这些基本概念。那为什么要用事件代理呢?其实这个话题完全可以写一篇博客自习研究,我这里就大概说一下我自己的想法。让我们先假设有一个div,其中包括了很多a,我们要做的就是对每一个a的click事件做不同处理(注意,这里有两个关键点,一个是“每一个a”,另一个是“不同处理”)。遇到这样一个问题,我想最开始的想法一定是对每个a注册一个函数,但是仔细想想,那么多a,以及那么多函数,这是多么棘手的一个问题啊?如果是统一的处理还很好办,大不了一个循环把所有a注册上一个处理函数,这样问题不就简单了,只是需要在这唯一一个处理函数里面进行判断做不同的处理罢了,这的确简化了我们最开始的想法,但是现在还存在一个可恶的循环啊,循环注册同一个事件处理函数,感觉真的好浪费啊!那么我们很自然的相当能不能就把这唯一一个事件处理函数注册到少量的元素上来达到目的呢?这大概就是事件代理的雏形吧,根据事件冒泡的冒泡性质(注意,并不是所有事件都会冒泡),我们可以把这唯一一个事件处理函数注册到div这个元素上,这时候我们只用往一个元素上注册唯一个事件处理函数,这不大大简化了我们最开始的想法?
有了事件代理的概念,我们再回过头想想我们的KISSY在注册事件的时候,对于同一类型的事件处理函数可以有很多,是不是很类似我们刚刚说的div中的a呢?但是我们的KISSY在注册这些事件的时候,不是每一个事件处理函数都是直接注册到事件目标对象上的,就像我们不准备用循环注册的方式在注册没一个a的处理函数,我们只是把他们暂存到侦听器里面(实际上,侦听器就是一个简单的数组),排成队列,然后依次调用即可,那我们div上的事件代理就像是我们KISSY中,同一类型的那个唯一的事件处理函数,我们只将它直接注册到事件目标对象上。请看下图:

我们往事件目标对象上注册了三个事件处理函数,但是这三个处理函数只是存储在侦听器这个中,而侦听器连接到事件代理上,真正注册到事件目标对象上的处理函数只有这个事件代理函数。因此对于DOM事件,我们需要用DOM方法,往事件目标对象上注册事件,处理函数就是这个事件代理,当事件发生时,就会调用这个事件代理进行处理,在事件代理中,我们只需依次从侦听器中调用用户注册的时间处理函数,就可以完成所有用户注册的所有事件处理函数的调用。需要特别说明的是,我们这里的事件代理,使用事件类型进行区分的,也就是说每个类型的事件都有一个事件代理以及一个侦听器数组,这样的划分粒度,即保证了函数处理、存储的简洁性,也保证了事件较高的可维护性,例如:我们可以简单滴取消所有某一类型的事件处理函数,这个工作对于直接使用DOM方法注册的事件处理函数,很难简单做到,相信诸位都有深刻体会。
说了这么说事件目标对象,那么我们应该自习分析一下这个概念。从字面上上理解,可以分为事件目标,和事件对象这两个方面,同时这正是KISSY源代码中对于事件相关文件的组织方式。
对于事件目标,其主要特点就是能够注册事件、撤销注册以及触发事件,对于DOM元素,除了text/commen两种类型的节点,其他都满足这个特点所有的DOM Node都满足这个特点,包括text/comment节点,只是这两种节点在实际应用中都不大实用(感谢玉伯指正),例如可以用addEventListener等方法注册一个事件处理函数,可以用removeEventListener这个方法撤销一个事件处理函数,所有的DOM事件都可分为UI Event、MouseEvent、KeyEvent,他们在浏览器内部都有相应的触发机制,构成了事件触发,因此DOM元素天生就是做事件目标的料根据DOM规范描述,EventTarget就是一个Interface,包含 addEventListener,removeEventListner,dispatchEvent 三个方法,因此,实现了这3个方法的对象,就是 EventTarget,DOM Node就是这样的(再次感谢玉伯指正);而我们要实现自定义函数,那么自定义事件目标就必须满足以上刚刚说的三个方面,因此在KISSY的事件模型中,自定义事件和DOM事件做了统一,只要自定义事件完成了三个相关函数的实现,就可以称之为事件目标。
事件对象,主要就是事件发生时,携带事件相关信息的对象,我们知道在DOM事件中,自身就有这个对象,只是这个对象存在诸多的兼容性问题,最基本的一个就是在IE8以下(包括),DOM事件发生时,这个事件对象是存储在window.event中,而W3C标准的浏览器,则是作为默认参数传递给事件处理函数,因此这类的不一致情况,导致了诸多了事件对象的兼容性问题,那么KISSY中为了处理这类的不兼容性,就用专门的fix对事件对象进行了修复,并提供了几个方便的事件对象函数的封装,例如阻止默认行为(preventDefault)、停止传播(stopPropagation)、终止事件(halt,包括停止事件传播和阻止默认行为)、立即停止传播(stopImmediatePropagation,与stopPropagation的主要区别是,该方法不仅阻止事件传播,还阻止之后侦听器中后续函数的发生)。我们的事件代理在事件触发时调用,其做的第一件事儿就是取出事件对象,然后传递给EventObject构造函数,构造出经过修复后的事件对象,这里的修复主要是参照W3C标准进行。文章最开始说到有一个网友提到了一个问题,其实这个问题就是和这个有关,他认为每次事件发生时,调用事件代理都需要构造一个EventObject,必然会影响性能,因此我也对这个性能有所担心,但是考虑到通常的事件并不会很高平率的触发,因此感觉这个性能问题影响不大,因此做了以下测试页:
请猛击这里查看DEMO
这个DEMO里,我用createEvent方法,创建了一个click事件,然后把这个事件分配给一个checkbox(以让大家能看见这个click的确发生了),一方面我们用传统的方法注册事件,不处理事件对象;另一方面,我们用KISSY的方法注册事件,这里的事件处理函数主要是处理一个进度条,当进度条完成后,记录消耗时间。因此,我们可以模拟大量点击事件,来考虑事件处理的性能问题,同时对于大量点击的模拟,我们加入一个延迟,可以模拟事件频发程度。对于这个DEMO,我们只能用非IE的Firefox和Chrome等浏览器测试(Opera也不支持),对于具体结果,大家可以先自己测试,观察一下。
点击Start按钮,可以看见右侧原本为选中的复选框已经选中,表示我们模拟的click事件已经有效了,我们如果用delay值为0的设置开始测试,会发现传统方式完成1000次点击、处理的时间在300ms左右,而KISSY方式消耗时间则在500ms以内(Chrome 6.0.472.25 dev),还是有尽40%的性能差异;当delay值设置增大时,两者的差距越来越小,因此可以看见两种方式的确有性能差异,但是当delay时间值增大,即浏览器有时间进行更复杂运算时,两者的差异已经很小,因此EventObject的创建有性能消耗,但是如果函数的调用没有如此频繁时及有足够的delay时间时,两者基本没有性能差异。因此可以得出结论,如果我们在实际使用中,KISSY的事件处理方式并不会造成较大的性能影响,我们用的主要事件很少会频发次数很高,今后可以再详细测试一下类似MouseMove的事件,同时可能还要考虑到具体的JavaScropt的具体运行环境。
今天主要的谈论集中在KISSY事件模型中的事件处理方式以及事件目标对象的概念,下次我们继续说说KISSY对一些特殊事件的处理方式,比如我们熟知的只有IE系列才拥有的MouseEnter/MouseLeave事件等,到此,我们对整个KISSY的事件模型和体系应该有大致了解,如果感兴趣的话,欢迎fork我们的源代码进行属于自己的开发,说不定你优秀的代码也能merge到KISSY的主分支中!
- EOF -
update. 2010.08.10 感谢网友hi,给出了支持IE测试的地址,欢迎强势围观。
我在你的demo上修改了一点点,可以支持ie了 代码见 http://www.nci365.com/test/e1.html
http://www.nci365.com/test/e2.html
e1.html和e2.html只有一句不同,见第119和120行
这两个比较下很有意思,让事件触发不是同一时刻并发,而是每个事件错开1ms触发,得到的测试结果就迥然不同。
0 Comments