事件检测,即检测某一事件在不同的浏览器中是否存在(可用),这在编写Javascript的过程中也非常重要,如mouseenter/mouseleave事件虽然实用,但并不是所有浏览器都提供了标准的支持,因此需要自己手动模拟,即:
function addEvent(element, name, handler) { if (name == 'mouseenter' && !hasEvent(name, element)) { //通过其他手段模拟mouseenter事件 } //正常的事件注册 };本文就重点讲述以上代码中hasEvent的具体实现。
基本方案关于事件的最基本检测方式,则需要从事件的注册方法开始说。
事件通常有3种注册方式,其中之一就是内联式,即在HTML中通过属性的方式声明事件,比如:
<button>CLICK ME</button>以上代码创建了一个button标签,并注册了click事件。
另一个方案是通过直接给onclick赋值来注册事件:
document.getElementById('myButton').onclick = function() { alert('CLICKED!'); };从上面两种注册事件的方式可以发现,其实onclick是button标签的一种属性(attribute),通过对其赋值可以完成事件的注册。
因此,最基本的事件检测方案,就是通过检查on[事件名]属性是否存在于DOM元素之中,因此有最简单的一个版本:
function hasEvent(name, element) { name = name.indexOf('on') ? 'on' + name : name; element = element || document.createElement('div'); var supported = name in element; };需要注意的是,事件是对on[事件名]的形式作为元素的属性而存在的,因此从通用性上考虑,在必要的时候对事件名补上'on'即可。另外由于是一个通用的判断事件是否可用的函数,当没有给定具体的元素时,可以使用最广泛应用的div元素作为替代。
部分标签特有事件有些事件是一些元素特有的,通常包括以下几个:
form独有事件:submit、reset input独有事件:change、select img独有事件:load、error、abort考虑到这些事件的存在,使用div元素有时会得到错误的结果,因此在创建一个通用的替代用元素时,可以使用一个字典来维护需要创建的元素标签名:
var hasEvent = (function() { var tags = { onsubmit: 'form', onreset: 'form', onselect: 'input', onchange: 'input', onerror: 'img', onload: 'img', onabort: 'img' }; return function(name, element) { name = name.indexOf('on') ? 'on' + name : name; element = element || document.createElement(tags[name] || 'div'); supported = name in element; } })();使用闭包将tags作为静态的字典使用,可以在一定程度上减少对象生成的开销。
DOM污染DOM元素之所以会有类似onclick的属性,是因为在DOM元素对象的中有这个属性,由于Javascript弱类型机制,外部代码可以通过对__proto__添加属性而影响hasEvent函数的结果,如以下代码在Firefox和Chrome中就会产生错误的结果:
document.createElement('div').__proto__.ontest = function() {}; var supported = hasEvent('test', document.createElement('div')); //true在上面的示例中,虽然在修改__proto__属性和调用hasEvent时,使用的是不同的div对象,但由于__proto__的实质是原型链中的对象,因此会影响到所有的div对象。
为了处理这种情况,需要尝试将__proto__属性中相应的属性进行删除,由于原生类型的属性带有DontDelete标记,是无法使用delete关键字进行删除的,因此对hasEvent函数附加以下的逻辑就可以更安全地判断:
var temp; if (supported && (temp = proto[name]) && delete proto[name]) { supported = name in element; proto[name] = temp; }逻辑很简单,尝试把__proto__中有可能附加上去的删了再试一试,当然别忘了再把原来的值变回去。
Firefox开始BUG很遗憾,前文提供的hasEvent函数并不能在Firefox完美工作,在Firefox中运行以下代码将得到false的结果:
alert('onclick' in document.documentElement); //Firefox弹出false因此,需要再次改造hasEvent函数以支持Firefox。在多数浏览器中,当元素使用内联方式注册了事件之后,可以通过element.on[事件名]来获取注册在上面的函数对象,例如:
<button ontest="alert('TEST!');">CLICK ME</button> <script type="text/javascript"> var button = document.getElementById('test'); alert(typeof button.onclick); //弹出function alert(typoef button.ontest); //弹出string </script>因此,只需要通过Javascript将一个表示函数的字符串挂载到on[事件名]属性(attribute)上,再去获取并判断是否得到了一个函数对象即可。
因此hasEvent函数在前文提供的方法返回false时,可以额外增加以下的代码以进一步确定事件是否存在:
if (!supported) { element.setAttribute(name, 'return;'); supported = typeof element[name] == 'function'; } Firefox继续BUG到现在为止,已经可以在兼容多数浏览器的情况下检测各DOM元素的事件,但是对于window对象的事件检测还没有一个完整的方案。