事件模型在各浏览器中存在差异

标准参考


根据 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' 也是。

(0)

相关推荐

  • ASP.NET的事件模型(很适合学习的文章)

    在Default.aspx的页面中第一行是一条页面指令: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AspxEventsModel._Default" %> 其中CodeBehind属性指定代码隐藏页面的名称,Inherits指定所属的命名空间和类,AutoEventWireup属性是可以

  • js事件模型与自定义事件实例解析

    JavaScript 一个最简单的事件模型,需要有事件绑定与触发,还有事件删除. var eventModel = { list: {}, bind: function () { var args = [].slice.call(arguments), type = args[0], handlers = args.slice(1); if (typeof type === 'string' && handlers.length > 0) { for (var i = 0; i &l

  • Javascript基础知识(一)核心基础语法与事件模型

    一.Javascript核心基础语法 1.Javascript是一门增加交互效果的编程语言,它最初由网景公司发明,最后提交给ECMA(欧洲计算机制造商协会),ECMA将Javascript标准化,其命名为Javascript. 2.Javascript是一门解释性语言,无需编译就可以直接在浏览器下运行. 3.Javascript的用途? 1.可以控制网页中所有元素,增加.删除.修改元素的属性. 2.可以在html中放入动态文本. 3.响应用户在使用网页时产生的事件. 4.校验用户输入的数据. 5

  • javascript事件模型实例分析

    本文实例讲述了javascript事件模型的用法.分享给大家供大家参考.具体分析如下: 一.事件模型 冒泡型事件(Bubbling):事件由叶子节点沿祖先节点一直向上传递到根节点 捕获型事件(Capturing):由DOM树最顶元素一直到最精确的元素,与冒泡型事件相反 DOM标准事件模型:DOM标准既支持冒泡型事件,也支持捕获型事件,可以说是两者的结合体,首先是捕获型,接着冒泡传递 二.事件对象 在IE浏览器中事件对象是window的一个属性,在DOM标准中,event必须作为唯一的参数传给事件

  • javascript事件捕获机制【深入分析IE和DOM中的事件模型】

    本文实例分析了javascript事件捕获机制.分享给大家供大家参考,具体如下: 1.什么是事件冒泡? 在排序算法中,我们学过冒泡排序法,所谓冒泡就是让底层的东西浮出水面,对于事件冒泡也同样是如此, 下面我们来看一个例子来说明什么是事件冒泡. <div> <button>测试</button> </div> <script> $("div").bind("click",function(){alert(&q

  • 带你快速理解javascript中的事件模型

    javascript中有两种事件模型:DOM0,DOM2.而对于这两种的时间模型,我一直不是非常的清楚,现在通过网上查阅资料终于明白了一些. 一.  DOM0级事件模型 DOM0级事件模型是早期的事件模型,所有的浏览器都是支持的,而且其实现也是比较简单.代码如下: <p id = 'click'>click me</p> <script> document.getElementById('click').onclick = function(event){ alert(

  • javascript事件模型介绍

    一.原始事件模型 其事件类型:分为"输入事件(如onclicki)"和"语义事件(如onsubmit)" 事件程序的注册可以以下几种方式: 1.JS代码作为HTML性质值 复制代码 代码如下: <input type="button" value="Press me" onclick="alert('thanks');" 2.事件处理程序作为JS属性 附注:文档中的每个HTML元素在文档树中都有一个相

  • 关于javascript DOM事件模型的两件事

    事件捕捉(Event Capture)的实现问题 W3C DOM Level2的事件模型规范中,事件在DOM树中的传播过程(从根节点到目标节点)被分为了两个阶段:捕捉(Capture)和冒泡(Bubbling).下面这个图能大概的说明整个过程: (from W3C) 如果想创建一个捕捉事件,在支持W3C 事件模型的浏览器中,将addEventListener的第三个参数设为true就好了.例如: 复制代码 代码如下: document.getElementById('foo').addEvent

  • js DOM模型操作

    DOM模型中的节点:元素节点.文本节点.属性节点 例:<a href="http://www.cnblogs.com/shuz">私のdotnet小屋</a> (1)a是元素节点 (2)"私のdotnet小屋"是文本节点 (3)href="http://www.cnblogs.com/shuz"是属性节点 DOM节点的属性 属性 类型 说明 nodeName String 节点名称,根据节点的类型而定义 nodeValue

  • 事件模型在各浏览器中存在差异

    标准参考 根据 W3C DOM 2 Events 描述,EventTarget 接口被所有支持 DOM 事件模型的节点(Node)实现. 该接口提供了 'addEventListener' 和 'removeEventListener' 方法,用来绑定或解绑一个 EventListeners 接口到一个 EventTarget. DOM 2 Events 中定义了 Event 接口,用来提供事件的上下文信息,它提供了若干标准属性和方法. 实现 Event 接口的对象一般作为第一个参数传入事件处理

  • 元素的内联事件处理函数的特殊作用域在各浏览器中存在差异

    标准参考 无. 问题描述 在一个元素的属性中绑定事件,实际上就创建了一个内联事件处理函数(如<h1 onclick="alert(this);"...>...</h1>),内联事件处理函数有其特殊的作用域链,并且各浏览器的实现细节也有差异. 造成的影响 如果在元素的内联事件处理函数中使用的变量或调用的方法不当,将导致脚本运行出错. 受影响的浏览器 所有浏览器 问题分析 1. 内联事件处理函数的作用域链 与其他函数不同,内联事件处理函数的作用域链从头部开始依次是:

  • 动态创建样式表在各浏览器中的差异测试代码

    复制代码 代码如下: <!doctype html> <head></head> <body> <span id="con">xxx</span> <script> var css = document.createElement('style'); css.setAttribute('type', 'text/css'); var cssText = 'span{color:Red;}'; if(cs

  • IE、FF、Chrome浏览器中的JS差异介绍

    因为浏览器公司各自为利益考虑,到目前为止各浏览器的HTML标准或是JS标准都还未统一.在平常的开发中,我们常使用的JS框架基本已经帮我们处理好了JS在各浏览器中的差异,但作为一个开发人员,还是有需要了解JS在浏览器中的差异. FF.Chrome:没有window.event对象 FF.Chrome:没有window.event对象,只有event对象,IE里只支持window.event,而其他主流浏览器两者都支持,所以一般写成:function handle(e){e = e || event

  • setTimeout与setInterval在不同浏览器下的差异

    .(新手可能认为setTimeout与setInterval是javascript函数,这是错误的.新手容易将javascript对象函数与DOM对象方法混淆.) 先来一段代码,大家猜猜在各种浏览器下的结果会是怎么样的呢? 复制代码 代码如下: function f(){ var s = 'arguments.length:'+arguments.length+'; '; for(var i=0,n=arguments.length;i< n;i++){ s += ' ['+i+']:'+arg

  • 详解JS浏览器事件模型

    什么是事件 我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢? 事件驱动通俗地来说就是什么都抽象为事件. 一次点击是一个事件 键盘按下是一个事件 一个网络请求成功是一个事件 页面加载是一个事件 页面报错是一个事件 浏览器依靠事件来驱动APP运行下去,如果没有了事件驱动,那么APP会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石. 一个简单的例子 其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态. 我们需要根据这个事件自己去完

  • 在IE浏览器中resize事件执行多次的解决方法

    这是个让人每次改变页面窗口的大小时很郁闷的方法,尤其在IE浏览器中,稍微动下窗口边框,就会触发很多次事件.更让人蛋疼的是在resize事件中包含某些页面内容处理或计算导致resize事件再次被触发的时候,IE会随机陷入假死状态. 网上找了好久,都是千律一篇的,到处都是转载的一个方法:以下是网上找到的一个解决方法: 复制代码 代码如下: var resizeTimer = null; $(window).resize(function() { if (resizeTimer) clearTimeo

  • IE浏览器中图片onload事件无效的解决方法

    故事模式实现是只加载当前浏览的照片和它下面的两张照片,加载照片的时候才会加载和渲染评论区,图片没有加载前会用一个一象素的图片占位,并用一个loading类将显示出一个loading背景图,判断在可视区的时候替换成真正的图片,图片加载成功后删除loading类. 问题出在最后面,测试的时候发现在IE下loading类无法删除,当时代码如下: 复制代码 代码如下: img.src = _src;img.src = _src;img.onload = function(){   _con.delCla

随机推荐