本文深入解析Kit.js框架中的事件管理机制,重点介绍ev()方法如何封装原生DOM事件。通过缓存注册句柄、维护执行顺序及统一Event对象属性,Kit.js解决了原生绑定在浏览器兼容性、事件顺序控制及注销困难等方面的痛点,为移动端与PC端开发提供统一的事件处理架构。
本章将重点介绍Kit.js框架中的事件管理模块,旨在用通俗的语言揭示主流JavaScript框架如何在内部实现独立的事件管理功能。
普通DOM事件绑定方式
(一)普通的Dom事件
通常,我们可以通过在HTML标签中直接编写事件属性来绑定事件。
<a onclick="alert(1)">测试</a>
或者,在获取DOM对象后通过属性进行绑定。
document.getElementById('a').onclick=function(){alert(1)}
此外,还可以使用二级事件绑定方式。
document.getElementById('a').addEventListener('click',function(){alert(1)},false)
或者通过script标签指定事件。
<script for="a" event="onclick">alert(1)</script>
W3C标准推荐上述第三种方式,即二级事件绑定,其核心目的是实现HTML与JavaScript的解耦,减少强依赖。
原生二级事件的局限性
(二)问题
然而,如果仅直接使用方式3进行JavaScript编程,仍会遇到以下问题:
1. 浏览器兼容性差异:IE系列与遵循W3C标准的浏览器在二级事件绑定的方法名及参数上存在不一致。
2. 事件状态不可见:通过二级事件绑定后,无法获知其他代码是否对同一元素绑定了事件,以及具体绑定了哪些事件和具体内容。
3. 执行顺序不确定:二级事件绑定的方法触发后,执行顺序并非严格按照绑定先后顺序,而是随机执行。但在某些场景下,我们需要确保方法按特定顺序触发。
4. 无法阻止后续事件:当同一元素的事件被触发后,W3C标准API不支持停止该元素上绑定的其他事件的继续触发,仅支持停止冒泡。
5. 注销困难:很多时候,我们通过匿名函数注册二级事件,未保留执行方法的句柄,导致难以通过removeEventListener注销该事件。
Kit.js事件管理方案
(三)Kit如何解决
JavaScript框架的存在正是为了解决上述问题,接下来我们将探讨Kit.js如何处理这些问题。
在Kit.js的API中,存在一个ev(config)方法。

该方法接受一个Map类型的对象,其中包含4个重要参数:
el:需要绑定的DOM元素。
String:事件类型。
fn:触发时执行的方法。
scope:可选参数,用于指定this指针;若未提供,则默认传入注册时的el作为this指针。
核心代码实现解析
(四)代码解析
让我们进一步查看具体的代码实现。
直接从核心部分开始分析。

如果传入参数不为空,则在传入参数的el上建立一个对象,用于保存Kit.js的事件注册信息evReg。
evReg对象包含两个子对象:evRegEv用于保存注册的事件。
在evRegEv对象中,保存一个key为当前注册事件、value为一个数组的结构。数组内按照先来后到的顺序放入ev方法传入的config参数。请注意,这里使用的是数组,因为数组可以保存先后顺序,这一点至关重要。
另一个子对象evRegFn用于保存事件触发时的匿名方法。


我们可以看到evRegFn是一个匿名事件函数,在开头,它会判断global变量window[me.CONSTANTS.KIT_EVENT_STOPIMMEDIATEPROPAGATION]是否等于true。如果是true,则直接返回,不再继续执行。
接着往下看,该函数接收事件触发的EV对象,并使用mergeIf方式附加多个对象,如target、currentTarget、relatedTarget,这些是为了处理浏览器兼容性问题。
此外,还创建了stopNow、stopDefault、stopGoOn方法,用于阻止事件继续触发。
下面这段代码是evRegFn的关键部分:循环遍历之前创建的evRegEv中的事件数组,按照先后顺序取出之前的ev方法传入的config参数,执行config参数中的方法。如果方法的返回值不为空,则返回该返回值。

最后进行浏览器兼容性处理,以二级事件的方式绑定我们的evRegFn匿名方法。
机制总结
(五)小结
简而言之,Kit.js通过一个自己的匿名方法,缓存了事件注册的句柄到数组中。这样既记住了事件的先后顺序,又提供了入口来查找之前注册的事件、参数和方法,同时针对浏览器兼容性做了兼容处理。
事件注销机制
(六)注销事件
有了Kit.js帮忙缓存事件句柄,注销事件变得简单。

你可以看到Kit.js通过直接对比,或者fn.toString对比,以及fn.toString().trim()的方式对比来找到对应的事件config,并从数组中删除。
Event对象增强与兼容
(七)事件增强
大家刚才也应该留意到Kit.js对系统的Event对象做了一个mergeIf操作。首先为什么要做mergeIf,因为系统的Event对象属性是Readonly的,不能覆盖,只能添加它没有的属性。
因此,Kit.js只能使用mergeIf。我们知道各个浏览器的事件对象Event Object存在不兼容性,所以需要Kit.js去修复这些不兼容。例如,IE没有target属性,只有srcElement,我们可以给它加上target属性,实现W3C标准的兼容。
当然,仅仅修复是不够的,很多时候,我们还需要给Event对象做一点小小的增强。
例如,在iPhone的touchdown、touchmove开发中,我们经常要取到单指的offset。而取单指的offset,又需要ev.targetTouches[0].clientX这样的代码。但是一旦在匿名函数中这样写,在PC上又不兼容了。
怎么办呢?没有关系,我们可以给Event Object mergeIf我们自己的属性。



例如firstFingerClientX等,这样我们就可以很简单地实现移动端与PC端开发的代码统一。
包括,下一篇要说的HTML5拖拽、高级手势事件都是基于这一基础之上架构的。
补充一下,为什么不象ExtJs那样new一个自己的Event,是因为:
1. 系统原生的对象有一定的继承关系,不想破坏。
2. 如果用自己的new Object,可能会造成代码脱离框架之后不可移植,需要再次改变代码内容。