DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 事件模型在各浏覽器中存在差異
事件模型在各浏覽器中存在差異
編輯:關於JavaScript     

標准參考

根據 W3C DOM 2 Events 描述,EventTarget 接口被所有支持 DOM 事件模型的節點(Node)實現。 該接口提供了 'addEventListener' 和 'removeEventListener' 方法,用來綁定或解綁一個 EventListeners 接口到一個 EventTarget。

DOM 2 Events 中定義了 Event 接口,用來提供事件的上下文信息,它提供了若干標准屬性和方法。 實現 Event 接口的對象一般作為第一個參數傳入事件處理函數,以用來提供當前事件相關的一些信息。

DOM 2 事件模型允許 DOM 實現支持事件的多模塊,如果有必要,事件模型允許附加新的事件模塊。 為了共同使用性的目的,DOM 會定義一個包含低級別的,依賴設備的事件模塊、一個 UI 邏輯事件模塊和一個文檔變化事件模塊的 UI 事件模塊。 第三方實現(比如浏覽器廠商)在定義一個新類型事件的時候,事件名稱一定不能使用大小寫無關的 'DOM' 字符串作開頭,該前綴是為未來的 DOM 事件模塊保留的。

DOM 2 已經定義了一個 UI 事件類型模塊和一個鼠標事件類型的模塊,它們分別對應 UIEvent 和 MouseEvent 接口。 這兩個接口提供了若干標准屬性和方法,以獲知事件發生時的一些信息。

關於 EventTarget 接口的詳細信息,請參考 DOM 2 Events 1.3. Event listener registration。

關於 Event 接口的詳細信息,請參考 DOM 2 Events 1.4. Event interface。

關於事件模塊的詳細信息,請參考 DOM 2 Events 1.6. Event module definitions。

問題描述

各浏覽器對元素綁定、解綁事件監聽器的方法,事件對象的獲取,以及 Event 對象的實現上存在差異。

造成的影響

如果使用某浏覽器特有的事件相關的屬性及方法編寫代碼,則可能造成兼容性問題,導致代碼報錯,功能失效。

受影響的浏覽器

所有浏覽器  

問題分析

1. 只有 IE Opera 支持使用 'attachEvent' 和 'detachEvent' 方法綁定和解綁事件監聽器

根據 DOM 2 Events 中描述,節點使用 'addEventListener' 和 'removeEventListener' 方法綁定和解綁事件監聽器,但 IE6 IE7 IE8 不支持這兩個方法, 而使用 'attachEvent' 和 'detachEvent' 方法作為替代方案,Opera 兩類方法都支持。Chrome Safari Firefox 只支持標准方法。

分析以下代碼:

<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>

依次點擊 'add event listener test' >> 'remove event listener test' >> 'add event listener test',測試各浏覽器對這些方法的支持,結果如下:

IE6 IE7 IE8 Opera Chrome Safari Firefox

關於 'addEventListener' 和 'attachEvent' 有幾點需要注意:

  • IE 不支持在捕獲階段觸發事件監聽器,'attachEvent' 方法沒有提供參數說明是否響應在捕獲階段觸發的事件;
  • 'addEventListener' 和 'attachEvent' 都可以注冊多個事件監聽器;
  • 在 Firefox Chrome Safari Opera 中給同一事件注冊同一個事件監聽器多次,重復注冊的會被丟棄;而在 IE 中重復注冊的事件監聽器會被重復執行多次;
  • 當給同一元素注冊了多個事件監聽器的時候,IE6 IE7 的事件監聽器執行順序是隨機的,IE8 是倒序的,Firefox Chrome Safari Opera 是順序的;
  • 當元素注冊的事件監聽器中有非法的事件監聽器時(非函數),在 IE Firefox 中會拋出異常,而在 Chrome Safari Opera 中則會忽略非法的事件監聽器,繼續執行其他的事件監聽器。

2. 各浏覽器獲取事件對象的差異

DOM 2 Events 中規定使用事件監聽器的第一個參數作為事件對象,而 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>

以上代碼組合不同的事件監聽器綁定方式和事件對象獲取方法,測試各浏覽器的支持程度。

依次點擊 'attachEvent' >> 'addEventListener' >> 'onClick',結果如下:

IE6 IE7 IE8 Chrome Safari Opera Firefox

匯總測試結果如下表: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 方法注冊事件監聽器的時候,IE 才支持使用事件監聽器傳入的第一個參數作為事件對象的方式;
  • Chrome Safari Opera 兩種獲取事件對象的方式都支持;
  • Firefox 只支持獲取事件對象的標准方式。

3. 各浏覽器中事件對象的屬性和方法的差異

IE 對事件對象的標准屬性和方法支持有限,針對大部分屬性和方法,IE 都提供了一套替代非標准的替代方案; 而 Firefox Chrome Safari Opera 除了全面支持事件對象的標准屬性和方法外,還在不同程度上支持了 IE 提供的非標准替代方案。

以下代碼檢測了 Event、UIEvent、MouseEvent 接口以及事件對象的非標准屬性在各浏覽器下的支持程度:

<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>

執行以上測試代碼,分別點擊 'Interface Event' 按鈕、'Interface UIEvent & MouseEvent' 按鈕和圖片,鼠標移到圖片上再移開,在文本框中輸入 'a',得到結果整理如下表: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 支持事件對象的所有非標准屬性,不支持除 'type' 外 Event 接口的所有方法屬性及方法,不支持 UIEvent 接口的所有屬性和方法,不支持 MouseEvent 接口的 'metaKey'、'relatedTarget' 屬性和 'initMouseEvent' 方法;
  • Chrome Safari Opera 支持事件對象的所有標准及非標准的屬性和方法;
  • Firefox 支持事件對象的所有標准屬性和方法,但僅支持非標准屬性中的 'cancelBubble' 和 'keyCode'。

需要注意的是:

  • Firefox 不支持事件對象的 'returnValue' 屬性,測試樣例中雖然顯示 'returnValue' 值為 false,但這僅僅是因為給事件對象添加了 'returnValue' 屬性,並沒有起到取消事件默認動作的作用,這從地址欄可以看出,多了 '#' 號,這是 A 標簽的 'href' 屬性造成的。
  • 各浏覽器對 Event 接口的 'timeStamp' 屬性返回值都不同。

關於 IE 實現的事件對象非標准的屬性及方法的詳細信息,請參考 MSDN event Object。

關於 Firefox 對事件對象實現的詳細信息,請參考 MDC event。

解決方案

1. 使用特性判斷創建無兼容性問題的事件監聽器綁定和解綁函數

如:

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. 使用特性判斷獲得有效的事件對象

在事件監聽器中判斷傳入的第一個參數或 window.event 是否有效,如:

function handler(e){
	e = e || window.event;
}

3. 使用特性判斷使用與標准對應的非標准方法及屬性

盡管 IE 對事件對象的標准屬性和方法支持有限,但它自己實現的事件模型也基本能夠替代或實現標准屬性或方法的功能。

下表總結了部分與標准事件對象對應的,或可以實現標准屬性或方法功能的非標准屬性:

Standard Non-standard target srcElement preventDefault() returnValue stopPropagation() cancelBubble relatedTarget fromElement toElement

另,標准的 'clientX' 屬性和非標准的 'x' 屬性作用是相同的,同樣 'clientY' 和 'y' 也是。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved