事件模型在各瀏覽器中存在差異
標(biāo)準(zhǔn)參考
根據(jù) W3C DOM 2 Events 描述,EventTarget 接口被所有支持 DOM 事件模型的節(jié)點(diǎn)(Node)實(shí)現(xiàn)。 該接口提供了 'addEventListener' 和 'removeEventListener' 方法,用來綁定或解綁一個 EventListeners 接口到一個 EventTarget。
DOM 2 Events 中定義了 Event 接口,用來提供事件的上下文信息,它提供了若干標(biāo)準(zhǔn)屬性和方法。 實(shí)現(xiàn) Event 接口的對象一般作為第一個參數(shù)傳入事件處理函數(shù),以用來提供當(dāng)前事件相關(guān)的一些信息。
DOM 2 事件模型允許 DOM 實(shí)現(xiàn)支持事件的多模塊,如果有必要,事件模型允許附加新的事件模塊。 為了共同使用性的目的,DOM 會定義一個包含低級別的,依賴設(shè)備的事件模塊、一個 UI 邏輯事件模塊和一個文檔變化事件模塊的 UI 事件模塊。 第三方實(shí)現(xiàn)(比如瀏覽器廠商)在定義一個新類型事件的時(shí)候,事件名稱一定不能使用大小寫無關(guān)的 'DOM' 字符串作開頭,該前綴是為未來的 DOM 事件模塊保留的。
DOM 2 已經(jīng)定義了一個 UI 事件類型模塊和一個鼠標(biāo)事件類型的模塊,它們分別對應(yīng) UIEvent 和 MouseEvent 接口。 這兩個接口提供了若干標(biāo)準(zhǔn)屬性和方法,以獲知事件發(fā)生時(shí)的一些信息。
關(guān)于 EventTarget 接口的詳細(xì)信息,請參考 DOM 2 Events 1.3. Event listener registration。
關(guān)于 Event 接口的詳細(xì)信息,請參考 DOM 2 Events 1.4. Event interface。
關(guān)于事件模塊的詳細(xì)信息,請參考 DOM 2 Events 1.6. Event module definitions。
問題描述
各瀏覽器對元素綁定、解綁事件監(jiān)聽器的方法,事件對象的獲取,以及 Event 對象的實(shí)現(xiàn)上存在差異。
造成的影響
如果使用某瀏覽器特有的事件相關(guān)的屬性及方法編寫代碼,則可能造成兼容性問題,導(dǎo)致代碼報(bào)錯,功能失效。
受影響的瀏覽器
| 所有瀏覽器 |
|---|
問題分析
1. 只有 IE Opera 支持使用 'attachEvent' 和 'detachEvent' 方法綁定和解綁事件監(jiān)聽器
根據(jù) DOM 2 Events 中描述,節(jié)點(diǎn)使用 'addEventListener' 和 'removeEventListener' 方法綁定和解綁事件監(jiān)聽器,但 IE6 IE7 IE8 不支持這兩個方法, 而使用 'attachEvent' 和 'detachEvent' 方法作為替代方案,Opera 兩類方法都支持。Chrome Safari Firefox 只支持標(biāo)準(zhǔn)方法。
分析以下代碼:
<button id="add" type="button">add event listener test</button>
<button id="remove" type="button">remove event listener test</button>
<div id="info"></div>
<script type="text/javascript">
var add = document.getElementById("add"),
remove = document.getElementById("remove"),
showMsgA = function(){showMsg("attachEvent")},
showMsgB = function(){showMsg("addEventListener")};
if(add.attachEvent){
add.attachEvent("onclick",showMsgA);
remove.attachEvent("onclick",removeE);
}
if(add.addEventListener){
add.addEventListener("click",showMsgB,false);
remove.addEventListener("click",removeE,false);
}
function removeE(){
if(add.detachEvent){
add.detachEvent("onclick",showMsgA);
showMsg("detachEvent");
}
if(add.removeEventListener){
add.removeEventListener("click",showMsgB,false);
showMsg("removeEventListener");
}
}
function showMsg(method){
document.getElementById("info").innerHTML += ("support " + method + "<br />");
}
</script>
依次點(diǎn)擊 'add event listener test' >> 'remove event listener test' >> 'add event listener test',測試各瀏覽器對這些方法的支持,結(jié)果如下:
| IE6 IE7 IE8 | ![]() |
|---|---|
| Opera | ![]() |
| Chrome Safari Firefox | ![]() |
關(guān)于 'addEventListener' 和 'attachEvent' 有幾點(diǎn)需要注意:
- IE 不支持在捕獲階段觸發(fā)事件監(jiān)聽器,'attachEvent' 方法沒有提供參數(shù)說明是否響應(yīng)在捕獲階段觸發(fā)的事件;
- 'addEventListener' 和 'attachEvent' 都可以注冊多個事件監(jiān)聽器;
- 在 Firefox Chrome Safari Opera 中給同一事件注冊同一個事件監(jiān)聽器多次,重復(fù)注冊的會被丟棄;而在 IE 中重復(fù)注冊的事件監(jiān)聽器會被重復(fù)執(zhí)行多次;
- 當(dāng)給同一元素注冊了多個事件監(jiān)聽器的時(shí)候,IE6 IE7 的事件監(jiān)聽器執(zhí)行順序是隨機(jī)的,IE8 是倒序的,F(xiàn)irefox Chrome Safari Opera 是順序的;
- 當(dāng)元素注冊的事件監(jiān)聽器中有非法的事件監(jiān)聽器時(shí)(非函數(shù)),在 IE Firefox 中會拋出異常,而在 Chrome Safari Opera 中則會忽略非法的事件監(jiān)聽器,繼續(xù)執(zhí)行其他的事件監(jiān)聽器。
2. 各瀏覽器獲取事件對象的差異
DOM 2 Events 中規(guī)定使用事件監(jiān)聽器的第一個參數(shù)作為事件對象,而 IE Opera Chrome Safari 還支持通過 window.event 獲取事件對象。
分析以下代碼:
<button type="button" id="attach">attachEvent</button>
<button type="button" id="addE">addEventListener</button>
<button type="button" id="byclick">onClick</button>
<br />
INFO :
<div id="info"></div>
<script type="text/javascript">
function $(id){return document.getElementById(id);}
var attach = $("attach"),
addE = $("addE"),
byClick = $("byclick");
attach.attachEvent && attach.attachEvent("onclick", handler);
addE.addEventListener && addE.addEventListener("click", handler, false);
byClick.onclick = handler;
function handler(){
var src = window === this ? window.event.srcElement : this, type = src.innerHTML;
window.event && showMsg(window.event.type, type + " + window.event");
arguments[0] && showMsg(arguments[0].type, type + " + arguments[0]");
}
function showMsg(type, msg){
$("info").innerHTML += (msg + "(type : " + type + ")<br />");
}
</script>
以上代碼組合不同的事件監(jiān)聽器綁定方式和事件對象獲取方法,測試各瀏覽器的支持程度。
依次點(diǎn)擊 'attachEvent' >> 'addEventListener' >> 'onClick',結(jié)果如下:
| IE6 IE7 IE8 | ![]() |
|---|---|
| Chrome Safari | ![]() |
| Opera | ![]() |
| Firefox | ![]() |
匯總測試結(jié)果如下表:1
| 事件對象獲取方式 | IE6 IE7 IE8 | Chrome Safari | Opera | Firefox |
|---|---|---|---|---|
| window.event | Y | Y | Y | N |
| arguments[0] | Y2 | Y | Y | Y |
注1: Y 表示支持該事件對象獲取方式,N 表示不支持。
注2: 部分支持。
從上表出可以看出:
- 只有在使用 attachEvent 方法注冊事件監(jiān)聽器的時(shí)候,IE 才支持使用事件監(jiān)聽器傳入的第一個參數(shù)作為事件對象的方式;
- Chrome Safari Opera 兩種獲取事件對象的方式都支持;
- Firefox 只支持獲取事件對象的標(biāo)準(zhǔn)方式。
3. 各瀏覽器中事件對象的屬性和方法的差異
IE 對事件對象的標(biāo)準(zhǔn)屬性和方法支持有限,針對大部分屬性和方法,IE 都提供了一套替代非標(biāo)準(zhǔn)的替代方案; 而 Firefox Chrome Safari Opera 除了全面支持事件對象的標(biāo)準(zhǔn)屬性和方法外,還在不同程度上支持了 IE 提供的非標(biāo)準(zhǔn)替代方案。
以下代碼檢測了 Event、UIEvent、MouseEvent 接口以及事件對象的非標(biāo)準(zhǔn)屬性在各瀏覽器下的支持程度:
<button type="button" id="iEvent">Interface Event</button>
<button type="button" id="iUIMouse">Interface UIEvent & MouseEvent</button>
<input type="text" id="nosK" />
<p id="wrap" style="border:3px solid;padding:5px;width:500px;"><a id="nosCM" href="#"><img src="google.gif" alt="IE"/></a></p>
<br />
<table border="1">
<tbody>
<tr>
<th>Interface Event</th>
<td id="einfo"></td>
</tr>
<tr>
<th>Interface UIEvent<br/>&<br/>MouseEvent</th>
<td id="minfo"></td>
</tr>
<tr>
<th>Non-standard<br/>&<br/>click</th>
<td id="ncinfo"></td>
</tr>
<tr>
<th>Non-standard<br/>&<br/>mouseover mouseout</th>
<td id="nminfo"></td>
</tr>
<tr>
<th>Non-standard<br/>&<br/>keyCode</th>
<td id="nkinfo"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
function $(id){return document.getElementById(id);}
function addEvent(elem, type, handler, useCapture){
elem.addEventListener ? elem.addEventListener(type, handler, useCapture) :
elem.attachEvent("on" + type, handler);
}
addEvent($("iEvent"), "click", handleEvent, false);
addEvent($("iUIMouse"), "click", handleUIMouse, false);
addEvent($("nosCM"), "click", handleNoSClick, false);
addEvent($("wrap"), "click", function(){alert("P tag.");}, false);
addEvent($("nosCM"), "mouseover", handldNoSMouse, false);
addEvent($("nosCM"), "mouseout", handldNoSMouse, false);
addEvent($("nosK"), "keydown", handleNoSKey, false);
addEvent($("nosK"), "keypress", handleNoSKey, false);
addEvent($("nosK"), "keyup", handleNoSKey, false);
function handleEvent(e){
e = e || window.event;
var props = {
type : "type",
target : "target",
currentTarget : "currentTarget",
eventPhase : "eventPhase",
bubbles : "bubbles",
cancelable : "cancelable",
timeStamp : "timeStamp",
initEvent : "initEvent",
preventDefault : "preventDefault",
stopPropagation : "stopPropagation"
};
showMsg(props, e, $("einfo"));
}
function handleUIMouse(e){
e = e || window.event;
var props = {
view : "view",
detail : "detail",
initUIEvent : "initUIEvent",
screenX: "screenX",
screenY : "screenY",
clientX : "clientX",
clientY : "clientY",
ctrlKey : "ctrlKey",
shiftKey : "shiftKey",
altKey : "altKey",
metaKey : "metaKey",
button : "button",
relatedTarget : "relatedTarget",
initMouseEvent : "initMouseEvent"
}
showMsg(props, e, $("minfo"));
}
function handleNoSClick(e){
e = e || window.event;
e.returnValue = false;
e.cancelBubble = true;
var props = {
cancelBubble : "cancelBubble",
offsetX : "offsetX",
offsetY : "offsetY",
returnValue : "returnValue",
srcElement : "srcElement",
x : "x",
y : "y"
};
showMsg(props, e, $("ncinfo"));
}
function handldNoSMouse(e){
e = e || window.event;
var props = {
fromElement : "fromElement",
toElement : "toElement"
};
showMsg(props, e, $("nminfo"));
}
function handleNoSKey(e){
e = e || window.event;
$("nkinfo").innerHTML += "<strong>" + e.type + "</strong> : " + e.keyCode + "<br/>";
}
function showMsg(props, e, info){
var tmp = "", p, val;
with(e){
for(p in props) {
try{
val = eval(props[p]) + " [" + typeof eval(props[p]) + "]";
} catch (e) {
val = undefined;
}
tmp += "<strong>" + p + "</strong> : " + val + "<br />"
}
}
info.innerHTML = tmp;
}
</script>
執(zhí)行以上測試代碼,分別點(diǎn)擊 'Interface Event' 按鈕、'Interface UIEvent & MouseEvent' 按鈕和圖片,鼠標(biāo)移到圖片上再移開,在文本框中輸入 'a',得到結(jié)果整理如下表:3
| Interface & Non-standard | Property & method | IE6 IE7 IE8 | Chrome Safari Opera | Firefox |
|---|---|---|---|---|
| Interface Event | type | Y | Y | Y |
| target | N | Y | Y | |
| currentTarget | N | Y | Y | |
| eventPhase | N | Y | Y | |
| bubbles | N | Y | Y | |
| cancelable | N | Y | Y | |
| timeStamp | N | Y | Y | |
| initEvent | N | Y | Y | |
| preventDefault | N | Y | Y | |
| stopPropagation | N | Y | Y | |
| Interface UIEvent | view | N | Y | Y |
| detail | N | Y | Y | |
| initUIEvent | N | Y | Y | |
| Interface MouseEvent | screenX | Y | Y | Y |
| screenY | Y | Y | Y | |
| clientX | Y | Y | Y | |
| clientY | Y | Y | Y | |
| ctrlKey | Y | Y | Y | |
| shiftKey | Y | Y | Y | |
| altKey | Y | Y | Y | |
| metaKey | N | Y | Y | |
| button | Y | Y | Y | |
| relatedTarget | N | Y | Y | |
| initMouseEvent | N | Y | Y | |
| Non-standard | cancelBubble | Y | Y | Y |
| offsetX | Y | Y | N | |
| offsetY | Y | Y | N | |
| returnValue | Y | Y | N | |
| srcElement | Y | Y | N | |
| x | Y | Y | N | |
| y | Y | Y | N | |
| fromElement | Y | Y | N | |
| toElement | Y | Y | N | |
| keyCode | Y | Y | Y |
注3: Y 代表事件對象支持該屬性或方法,N 代表不支持。
從上表中可以看出:
- IE 支持事件對象的所有非標(biāo)準(zhǔn)屬性,不支持除 'type' 外 Event 接口的所有方法屬性及方法,不支持 UIEvent 接口的所有屬性和方法,不支持 MouseEvent 接口的 'metaKey'、'relatedTarget' 屬性和 'initMouseEvent' 方法;
- Chrome Safari Opera 支持事件對象的所有標(biāo)準(zhǔn)及非標(biāo)準(zhǔn)的屬性和方法;
- Firefox 支持事件對象的所有標(biāo)準(zhǔn)屬性和方法,但僅支持非標(biāo)準(zhǔn)屬性中的 'cancelBubble' 和 'keyCode'。
需要注意的是:
- Firefox 不支持事件對象的 'returnValue' 屬性,測試樣例中雖然顯示 'returnValue' 值為 false,但這僅僅是因?yàn)榻o事件對象添加了 'returnValue' 屬性,并沒有起到取消事件默認(rèn)動作的作用,這從地址欄可以看出,多了 '#' 號,這是 A 標(biāo)簽的 'href' 屬性造成的。
- 各瀏覽器對 Event 接口的 'timeStamp' 屬性返回值都不同。
關(guān)于 IE 實(shí)現(xiàn)的事件對象非標(biāo)準(zhǔn)的屬性及方法的詳細(xì)信息,請參考 MSDN event Object。
關(guān)于 Firefox 對事件對象實(shí)現(xiàn)的詳細(xì)信息,請參考 MDC event。
解決方案
1. 使用特性判斷創(chuàng)建無兼容性問題的事件監(jiān)聽器綁定和解綁函數(shù)
如:
function addEvent(elem, type, handler, useCapture){
elem.addEventListener ? elem.addEventListener(type, handler, useCapture) :
elem.attachEvent("on" + type, handler);
}
function removeEvent(elem, type, handler, useCapture){
elem.removeEventListener ? elem.removeEventListener(type, handler, useCapture) :
elem.detachEvent("on" + type, handler);
}
2. 使用特性判斷獲得有效的事件對象
在事件監(jiān)聽器中判斷傳入的第一個參數(shù)或 window.event 是否有效,如:
function handler(e){
e = e || window.event;
}
3. 使用特性判斷使用與標(biāo)準(zhǔn)對應(yīng)的非標(biāo)準(zhǔn)方法及屬性
盡管 IE 對事件對象的標(biāo)準(zhǔn)屬性和方法支持有限,但它自己實(shí)現(xiàn)的事件模型也基本能夠替代或?qū)崿F(xiàn)標(biāo)準(zhǔn)屬性或方法的功能。
下表總結(jié)了部分與標(biāo)準(zhǔn)事件對象對應(yīng)的,或可以實(shí)現(xiàn)標(biāo)準(zhǔn)屬性或方法功能的非標(biāo)準(zhǔn)屬性:
| Standard | Non-standard |
|---|---|
| target | srcElement |
| preventDefault() | returnValue |
| stopPropagation() | cancelBubble |
| relatedTarget | fromElement toElement |
另,標(biāo)準(zhǔn)的 'clientX' 屬性和非標(biāo)準(zhǔn)的 'x' 屬性作用是相同的,同樣 'clientY' 和 'y' 也是。
相關(guān)文章
純JS實(shí)現(xiàn)旋轉(zhuǎn)圖片3D展示效果
本文給大家分享的是純js實(shí)現(xiàn)的類似flash里的圖片環(huán)繞旋轉(zhuǎn)效果,非常炫酷,有需要的小伙伴可以參考下。2015-04-04
JavaScript統(tǒng)計(jì)字符串中每個字符出現(xiàn)次數(shù)完整實(shí)例
這篇文章主要介紹了JavaScript統(tǒng)計(jì)字符串中每個字符出現(xiàn)次數(shù)的方法,以完整實(shí)例形式分析了JavaScript針對字符串中字符的遍歷操作相關(guān)技巧,需要的朋友可以參考下2016-01-01
深入理解ES6 Promise 擴(kuò)展always方法
本篇文章主要介紹了ES6 Promise 擴(kuò)展always方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
ES6知識點(diǎn)整理之?dāng)?shù)組解構(gòu)和字符串解構(gòu)的應(yīng)用示例
這篇文章主要介紹了ES6知識點(diǎn)整理之?dāng)?shù)組解構(gòu)和字符串解構(gòu)的應(yīng)用,結(jié)合實(shí)例形式分析了ES6數(shù)組解構(gòu)和字符串解構(gòu)的實(shí)現(xiàn)方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-04-04
基于Web Audio API實(shí)現(xiàn)音頻可視化效果
這篇文章主要介紹了基于Web Audio API實(shí)現(xiàn)音頻可視化效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06








