In the previous article, we introduced several new browser feature detection solutions and their specific purposes in jQuery 1.4 version. This article will focus on events and introduce a relatively complete and universal event detection solution.
Event detection is to detect whether an event exists (available) in different browsers. This is also very important in the process of writing Javascript. For example, although the mouseenter/mouseleave event is practical, it is not available for all browsers. All browsers provide standard support, so you need to simulate it manually, that is:
function addEvent(element, name, handler) { if (name == 'mouseenter' && !hasEvent(name, element)) { //通过其他手段模拟mouseenter事件 } //正常的事件注册 };
This article will focus on the specific implementation of hasEvent in the above code.
As for the most basic detection method of events, you need to start with the event registration method.
There are usually 3 ways to register events, one of which is inline, that is, declaring the event through attributes in HTML, such as:
<button onclick="alert('CLICKED!');">CLICK ME</button>
The above code creates a button tag and registered the click event.
Another solution is to register the event by directly assigning a value to onclick:
document.getElementById('myButton').onclick = function() { alert('CLICKED!'); };
From the above two methods of registering events, we can find that onclick is actually an attribute of the button tag. ), event registration can be completed by assigning a value to it.
Therefore, the most basic event detection solution is to check whether the on[event name] attribute exists in the DOM element, so there is the simplest version:
function hasEvent(name, element) { name = name.indexOf('on') ? 'on' + name : name; element = element || document.createElement('p'); var supported = name in element; };
Required Note that events exist as attributes of elements in the form of on[event name], so from the perspective of versatility, just add 'on' to the event name when necessary. In addition, since it is a general function for judging whether an event is available, when no specific element is given, the most widely used p element can be used instead.
Some events are unique to some elements, usually including the following:
Form-unique events: submit, reset
input unique events: change, select
img unique events: load, error, abort
Considering the existence of these events, using p elements will sometimes get wrong results, so when creating a universal alternative element, you can use a dictionary to maintain the element tag names that need to be created:
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] || 'p'); supported = name in element; } })();
Using closures to use tags as static dictionaries can reduce the cost of object generation to a certain extent.
The reason why DOM elements have onclick-like attributes is because there is this attribute in the __proto__ of the DOM element object. Due to the Javascript weak type mechanism, external code can pass Adding attributes to __proto__ affects the results of the hasEvent function. For example, the following code will produce wrong results in Firefox and Chrome:
document.createElement('p').__proto__.ontest = function() {}; var supported = hasEvent('test', document.createElement('p')); //true
In the above example, although the __proto__ attribute is modified When calling hasEvent, a different p object is used, but since the essence of __proto__ is an object in the prototype chain, it will affect all p objects.
In order to handle this situation, you need to try to delete the corresponding attributes in the __proto__ attribute. Since the native type attributes are marked with DontDelete, they cannot be deleted using the delete keyword, so the hasEvent function You can make a safer judgment by adding the following logic:
var temp; if (supported && (temp = proto[name]) && delete proto[name]) { supported = name in element; proto[name] = temp; }
The logic is very simple. Try to delete the ones that may be appended in __proto__ and try again. Of course, don’t forget to add the original ones. The value changes back.
Unfortunately, the hasEvent function provided above does not work perfectly in Firefox. Running the following code in Firefox will get a false result:
alert('onclick' in document.documentElement); //Firefox弹出false
Therefore, the hasEvent function needs to be modified again to support Firefox. In most browsers, when an element registers an event inline, you can get the function object registered on it through element.on[event name], for example:
<button id="test" onclick="alert('CLICKED!');" 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>
Therefore, just Mount a string representing the function to the on[event name] attribute through Javascript, and then obtain and determine whether you have obtained a function object.
Therefore, when the hasEvent function returns false in the method provided above, you can add the following additional code to further determine whether the event exists:
if (!supported) { element.setAttribute(name, 'return;'); supported = typeof element[name] == 'function'; }
to So far, it is possible to detect events of each DOM element while being compatible with most browsers, but there is no complete solution for event detection of window objects.
For IE series, Chrome and Safari, you can use the simple on[event name] in window to detect whether the event exists, so the original hasEvent function that prevents DOM pollution can complete the task well.
Only on Firefox, the following code will give wrong results:
alert('onload' in window); //Firefox弹出false alert('onunload' in window); //Firefox弹出false alert('onerror' in window); //Firefox弹出false
What is both fortunate and outrageous is that Firefox can strangely detect the above on elements such as p 3 events, which directly leads to errors in detecting events on ordinary DOM elements, and also allows us to detect events on the window. Fortunately, most developers will not check whether there is an unload event on an element such as p. Therefore, the hasEvent function is added to direct the events on the window to a p object to detect some events:
if (!supported) { if (!element.setAttribute || !element.removeAttribute) { element = document.createElement('p'); } element.setAttribute(name, 'return;'); supported = typeof element[name] == 'function'; element.removeAttribute(name); }
At this point, a relatively complete hasEvent function is completed, although there are still some problems on Firefox, such as the following Code:
alert(hasEvent('unload', document.createElement('p')); //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] || 'p'); 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,但是p有unload事件(OTL) if (!supported) { if (!element.setAttribute || !element.removeAttribute) { element = document.createElement('p'); } 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', 'p', function (p) { p.id = 'new'; });
对于其他事件的检测,同样只需要制作出一个特定的change函数即可。
这个事件在文档加载完成时触发,但不需要等待图片等资源下载,多数Javascript框架的document.ready都会试图使用这个事件。
无论是hasEvent函数还是hasMutationEvent函数都无法检测到这个事件,但是问题不大,因为:
这事件和onload一样,页面的生命周期中只会触发一次,不会频繁使用。
所有支持addEventListener的浏览器都支持这个事件(包括IE9),因此判断简单。
所以这个事件被排除在了本文讨论范围之外,具体的可以查看各框架的document.ready函数的实现方式。