对于IE系列、Chrome和Safari,都可以使用简单的on[事件名] in window检测事件是否存在,因此原有的提供防止DOM污染后的hasEvent函数可以很好地完成任务。
唯有Firefox上,以下代码会给出错误的结果:
alert('onload' in window); //Firefox弹出false alert('onunload' in window); //Firefox弹出false alert('onerror' in window); //Firefox弹出false值得庆幸也值得愤怒的是,Firefox很诡异地可以在div等元素上检测到以上3个事件,这直接导致对普通DOM元素检测事件的错误,也导致我们可以检测到window上的事件。好在一般开发者也不会去一个div之类的元素上检测是否有unload事件。因此补充hasEvent函数,将window上的事件导向一个div对象来检测部分事件:
if (!supported) { if (!element.setAttribute || !element.removeAttribute) { element = document.createElement('div'); } element.setAttribute(name, 'return;'); supported = typeof element[name] == 'function'; element.removeAttribute(name); }至此,一个较为完整的hasEvent函数完成了,虽然在Firefox上还存在一些问题,比如以下的代码:
alert(hasEvent('unload', document.createElement('div')); //Firefox弹出true但是在99%的应用场合之下,这个函数是可以正确的工作的。
添加缓存为了进一步提高hasEvent的工作效率,考虑到DOM规范规定的事件数量不多,可以对通用的事件(即不指定检测的元素对象)检测添加缓存机制。
添加了缓存之后,最终完整的hasEvent函数如下:
var hasEvent = (function () { var tags = { onsubmit: 'form', onreset: 'form', onselect: 'input', onchange: 'input', onerror: 'img', onload: 'img', onabort: 'img' }, cache = {}; return function(name, element) { name = name.indexOf('on') ? 'on' + name : name; //命中缓存 if (!element && name in cache) { return cache[name]; } element = element || document.createElement(tags[name] || 'div'); var proto = element.__proto__ || {}, supported = name in element, temp; //处理显示在元素的__proto__上加属性的情况 if (supported && (temp = proto[name]) && delete proto[name]) { supported = name in element; proto[name] = temp; } //处理Firefox不给力的情况 //Firefox下'onunload' in window是false,但是div有unload事件(OTL) if (!supported) { if (!element.setAttribute || !element.removeAttribute) { element = document.createElement('div'); } element.setAttribute(name, 'return;'); supported = typeof element[name] == 'function'; element.removeAttribute(name); } //添加到缓存 cache[name] = supported; return supported; }; })(); Mutation Event是由DOM Level 2制定的一类特殊的事件,这些事件在某个元素为根的DOM树结构发生变化时触发,可以在这里看到具体的事件列表。
遗憾的是hasEvent函数无法检测到Mutation Event,因此对于此类事件,需要另一种较为复杂的事件检测方案。
从Mutation Event的列表中可以发现,此类事件的特点在于当DOM树结构发生变化时才会被触发,因此可以使用下面这套逻辑去检测:
准备一个标记位,默认为false。 创建出一个DOM树结构。 注册一个Mutation Event。 通过一定手段让这个DOM树变化,从而触发注册的事件。 在事件处理函数中,将标记位设为true。 返回标记位。具体的实现代码可以如下:
function hasMutationEvent(name, tag, change) { var element = document.createElement(tag), supported = false; function handler() { supported = true; }; //IE9开始支持addEventListener,因此只有IE6-8没有这个函数 //但是IE6-8已经确定不支持Mutation Event,所以有这个判断 if (!element.addEventListener) { return false; } element.addEventListener(name, handler, false); change(element); element.removeEventListener(name, handler, false); return supported; };例如需要检测DOMAttrModified事件是否存在,只需要用以下代码:
var isDOMAttrModifiedSupported = hasMutationEvent('DOMAttrModified', 'div', function (div) { div.id = 'new'; });对于其他事件的检测,同样只需要制作出一个特定的change函数即可。
DOMContentLoaded这个事件在文档加载完成时触发,但不需要等待图片等资源下载,多数Javascript框架的document.ready都会试图使用这个事件。
无论是hasEvent函数还是hasMutationEvent函数都无法检测到这个事件,但是问题不大,因为:
这事件和onload一样,页面的生命周期中只会触发一次,不会频繁使用。 所有支持addEventListener的浏览器都支持这个事件(包括IE9),因此判断简单。