細說瀏覽器特性檢測(2)-通用事件檢測
事件檢測,即檢測某一事件在不同的瀏覽器中是否存在(可用),這在編寫Javascript的過程中也非常重要,如mouseenter/mouseleave事件雖然實用,但并不是所有瀏覽器都提供了標準的支持,因此需要自己手動模擬,即:
function addEvent(element, name, handler) {
if (name == 'mouseenter' && !hasEvent(name, element)) {
//通過其他手段模擬mouseenter事件
}
//正常的事件注冊
};
本文就重點講述以上代碼中hasEvent的具體實現(xiàn)。
基本方案
關于事件的最基本檢測方式,則需要從事件的注冊方法開始說。
事件通常有3種注冊方式,其中之一就是內(nèi)聯(lián)式,即在HTML中通過屬性的方式聲明事件,比如:
<button onclick="alert('CLICKED!');">CLICK ME</button>
以上代碼創(chuàng)建了一個button標簽,并注冊了click事件。
另一個方案是通過直接給onclick賦值來注冊事件:
document.getElementById('myButton').onclick = function() {
alert('CLICKED!');
};
從上面兩種注冊事件的方式可以發(fā)現(xiàn),其實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[事件名]的形式作為元素的屬性而存在的,因此從通用性上考慮,在必要的時候?qū)κ录a上'on'即可。另外由于是一個通用的判斷事件是否可用的函數(shù),當沒有給定具體的元素時,可以使用最廣泛應用的div元素作為替代。
部分標簽特有事件
有些事件是一些元素特有的,通常包括以下幾個:
- form獨有事件:submit、reset
- input獨有事件:change、select
- img獨有事件:load、error、abort
考慮到這些事件的存在,使用div元素有時會得到錯誤的結(jié)果,因此在創(chuàng)建一個通用的替代用元素時,可以使用一個字典來維護需要創(chuàng)建的元素標簽名:
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作為靜態(tài)的字典使用,可以在一定程度上減少對象生成的開銷。
DOM污染
DOM元素之所以會有類似onclick的屬性,是因為在DOM元素對象的
在上面的示例中,雖然在修改__proto__屬性和調(diào)用hasEvent時,使用的是不同的div對象,但由于__proto__的實質(zhì)是原型鏈中的對象,因此會影響到所有的div對象。 為了處理這種情況,需要嘗試將__proto__屬性中相應的屬性進行刪除,由于原生類型的屬性帶有DontDelete標記,是無法使用delete關鍵字進行刪除的,因此對hasEvent函數(shù)附加以下的邏輯就可以更安全地判斷: 邏輯很簡單,嘗試把__proto__中有可能附加上去的刪了再試一試,當然別忘了再把原來的值變回去。 很遺憾,前文提供的hasEvent函數(shù)并不能在Firefox完美工作,在Firefox中運行以下代碼將得到false的結(jié)果: 因此,需要再次改造hasEvent函數(shù)以支持Firefox。在多數(shù)瀏覽器中,當元素使用內(nèi)聯(lián)方式注冊了事件之后,可以通過element.on[事件名]來獲取注冊在上面的函數(shù)對象,例如: 因此,只需要通過Javascript將一個表示函數(shù)的字符串掛載到on[事件名]屬性(attribute)上,再去獲取并判斷是否得到了一個函數(shù)對象即可。 因此hasEvent函數(shù)在前文提供的方法返回false時,可以額外增加以下的代碼以進一步確定事件是否存在: 到現(xiàn)在為止,已經(jīng)可以在兼容多數(shù)瀏覽器的情況下檢測各DOM元素的事件,但是對于window對象的事件檢測還沒有一個完整的方案。 對于IE系列、Chrome和Safari,都可以使用簡單的on[事件名] in window檢測事件是否存在,因此原有的提供防止DOM污染后的hasEvent函數(shù)可以很好地完成任務。 唯有Firefox上,以下代碼會給出錯誤的結(jié)果: 值得慶幸也值得憤怒的是,F(xiàn)irefox很詭異地可以在div等元素上檢測到以上3個事件,這直接導致對普通DOM元素檢測事件的錯誤,也導致我們可以檢測到window上的事件。好在一般開發(fā)者也不會去一個div之類的元素上檢測是否有unload事件。因此補充hasEvent函數(shù),將window上的事件導向一個div對象來檢測部分事件: 至此,一個較為完整的hasEvent函數(shù)完成了,雖然在Firefox上還存在一些問題,比如以下的代碼: 但是在99%的應用場合之下,這個函數(shù)是可以正確的工作的。 為了進一步提高hasEvent的工作效率,考慮到DOM規(guī)范規(guī)定的事件數(shù)量不多,可以對通用的事件(即不指定檢測的元素對象)檢測添加緩存機制。 添加了緩存之后,最終完整的hasEvent函數(shù)如下: Mutation Event是由DOM Level 2制定的一類特殊的事件,這些事件在某個元素為根的DOM樹結(jié)構(gòu)發(fā)生變化時觸發(fā),可以在這里看到具體的事件列表。 遺憾的是hasEvent函數(shù)無法檢測到Mutation Event,因此對于此類事件,需要另一種較為復雜的事件檢測方案。 從Mutation Event的列表中可以發(fā)現(xiàn),此類事件的特點在于當DOM樹結(jié)構(gòu)發(fā)生變化時才會被觸發(fā),因此可以使用下面這套邏輯去檢測: 具體的實現(xiàn)代碼可以如下: 例如需要檢測DOMAttrModified事件是否存在,只需要用以下代碼: 對于其他事件的檢測,同樣只需要制作出一個特定的change函數(shù)即可。 這個事件在文檔加載完成時觸發(fā),但不需要等待圖片等資源下載,多數(shù)Javascript框架的document.ready都會試圖使用這個事件。 無論是hasEvent函數(shù)還是hasMutationEvent函數(shù)都無法檢測到這個事件,但是問題不大,因為: 所以這個事件被排除在了本文討論范圍之外,具體的可以查看各框架的document.ready函數(shù)的實現(xiàn)方式。 哪位無聊就把所有的Mutation Event的檢測函數(shù)寫出來吧……var temp;
if (supported && (temp = proto[name]) && delete proto[name]) {
supported = name in element;
proto[name] = temp;
}
Firefox開始BUG
alert('onclick' in document.documentElement); //Firefox彈出false
<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>
if (!supported) {
element.setAttribute(name, 'return;');
supported = typeof element[name] == 'function';
}
Firefox繼續(xù)BUG
alert('onload' in window); //Firefox彈出false
alert('onunload' in window); //Firefox彈出false
alert('onerror' in window); //Firefox彈出false
if (!supported) {
if (!element.setAttribute || !element.removeAttribute) {
element = document.createElement('div');
}
element.setAttribute(name, 'return;');
supported = typeof element[name] == 'function';
element.removeAttribute(name);
}
alert(hasEvent('unload', document.createElement('div')); //Firefox彈出true
添加緩存
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
function hasMutationEvent(name, tag, change) {
var element = document.createElement(tag),
supported = false;
function handler() {
supported = true;
};
//IE9開始支持addEventListener,因此只有IE6-8沒有這個函數(shù)
//但是IE6-8已經(jīng)確定不支持Mutation Event,所以有這個判斷
if (!element.addEventListener) {
return false;
}
element.addEventListener(name, handler, false);
change(element);
element.removeEventListener(name, handler, false);
return supported;
};
var isDOMAttrModifiedSupported =
hasMutationEvent('DOMAttrModified', 'div', function (div) { div.id = 'new'; });
DOMContentLoaded
相關資源
相關文章
jquery中dom操作和事件的實例學習 仿yahoo郵箱登錄框的提示效果
最近把jquery中的dom操作和jquery中的事件和動畫的方法都大體測了一下。本來想細細的把每個方法都寫出來介紹下2011-11-11JavaScript jQuery 中定義數(shù)組與操作及jquery數(shù)組操作
這篇文章主要介紹了JavaScript jQuery 中定義數(shù)組與操作及jquery數(shù)組操作的相關資料,需要的朋友可以參考下2015-12-12jQuery實現(xiàn)加入收藏夾功能(主流瀏覽器兼職)
jQuery實現(xiàn)加入收藏夾功能,代碼比較簡單,兼容主流瀏覽器,下面給大家分享下2016-12-12Easyui在treegrid添加控件的實現(xiàn)方法
這篇文章主要介紹了Easyui在treegrid添加控件的實現(xiàn)方法,需要的朋友可以參考下2017-06-06