首页 > 代码库 > 第13章 事件

第13章 事件

 

JavaScript 与HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式的模型,支持页面的行为(JavaScript 代码)与页
面的外观(HTML 和CSS 代码)之间的松散耦合。

13.1 事件流

事件流描述的是从页面中接收事件的顺序。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕获流。

13.1.1 事件冒泡

IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)

<!DOCTYPE html>
<html>
  <head>
    <title>Event Bubbling Example</title>
  </head>
  <body>
    <div id="myDiv">Click Me</div>
  </body>
</html>

如果你单击了页面中的<div>元素,那么这个click 事件会按照如下顺序传播:

(1) <div>
(2) <body>
(3) <html>
(4) document

也就是说,click 事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后,click事件沿DOM树向上传播,在每一级节点上都会发生,直至传播到document 对象。

事件冒泡过程:

技术分享

所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5 及更早版本中的事件冒泡会跳过<html>元素(从<body>直接跳到document)。IE9、Firefox、Chrome 和Safari 则将事件一直冒泡到window 对象。

13.1.2 事件捕获

Netscape Communicator 团队提出的另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。如果仍以前面的HTML 页面作为演示事件捕获的例子,那么单击<div>元素就会以下列顺序触发click 事件。

(1)document
(2)<html>
(3)<body>
(4)<div>

在事件捕获过程中,document 对象首先接收到click 事件,然后事件沿DOM 树依次向下,一直传播到事件的实际目标,即<div>元素。

事件捕获的过程:

技术分享

虽然事件捕获是Netscape Communicator 唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox 目前也都支持这种事件流模型。尽管“DOM2 级事件”规范要求事件应该从document 对象开始传播,但这些浏览器都是从window 对象开始捕获事件的。

由于老版本的浏览器不支持,因此很少有人使用事件捕获。

13.1.3 DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。以前面简单的HTML 页面为例,单击<div>元素会按照图所示顺序触发事件。

 技术分享

在DOM 事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document 到<html>再到<body>后就停止了。下一个阶段是“处于目标”阶段,于是事件在<div>上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。 

多数支持DOM 事件流的浏览器都实现了一种特定的行为;即使“DOM2 级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox 和Opera 9.5 及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。

IE9、Opera、Firefox、Chrome 和Safari 都支持DOM 事件流;IE8 及更早版本不支持DOM 事件流。

13.2 事件处理程序

事件就是用户或浏览器自身执行的某种动作。诸如click、load 和mouseover,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,因此click 事件的事件处理程序就是onclick,load 事件的事件处理程序就是onload。为事件指定处理程序的方式有好几种。

13.2.1 HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML 特性来指定。这个特性的值应该是能够执行的JavaScript 代码。例如,要在按钮被单击时执行一些JavaScript,可以像下面这样编写代码:

<input type="button" value="Click Me" onclick="alert(‘Clicked‘)" />

由于这个值是JavaScript,因此不能在其中使用未经转义的HTML 语法字符,例如和号(&)、双引号("")、小于号(<)或大于号(>)。为了避免使用HTML 实体,这里使用了单引号。如果想要使用双引号,那么就要将代码改写成如下所示: 

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

在HTML 中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下面的例子所示: 

<script type="text/javascript">
  function showMessage(){
    alert("Hello world!");
  }
</script>
<input type="button" value="Click Me" onclick="showMessage()" />

单击按钮就会调用showMessage()函数。这个函数是在一个独立的<script>元素中定义的,当然也可以被包含在一个外部文件中。事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。 

这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。

<!-- 输出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">

通过event 变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this 值等于事件的目标元素。 

<!-- 输出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">

关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问document 及该元素本身的成员。这个函数使用with 像下面这样扩展作用域:

function(){
  with(document){
    with(this){
    //元素属性值
    }
  }
}

如此一来,事件处理程序要访问自己的属性就简单多了。下面这行代码与前面的例子效果相同: 

<!-- 输出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(value)">

如果当前元素是一个表单输入元素,则作用域中还会包含访问表单元素(父元素)的入口,这个函数就变成了如下所示: 

function(){
  with(document){
    with(this.form){
      with(this){
        //元素属性值
      }
    }
  }
}

实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段。例如: 

<form method="post">
  <input type="text" name="username" value="">
  <input type="button" value="Echo Username" onclick="alert(username.value)">
</form>

在HTML 中指定事件处理程序有两个缺点。首先,存在一个时差问题。因为用户可能会在HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件,因此很多HTML 事件处理程序都会被封装在一个try-catch 块中,以便错误不会浮出水面,如下面的例子所示: 

<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">

这样,如果在showMessage()函数有定义之前单击了按钮,用户将不会看到JavaScript 错误,因为在浏览器有机会处理错误之前,错误就被捕获了。 

另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同JavaScript引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。

通过HTML 指定事件处理程序的最后一个缺点是HTML 与JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和JavaScript 代码。而这正是许多开发人员摒弃HTML 事件处理程序,转而使用JavaScript 指定事件处理程序的原因所在。

13.2.2 DOM0 级事件处理程序

通过JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web 浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。要使用JavaScript 指定事件处理程序,首先必须取得一个要操作的对象的引用。

每个元素(包括window 和document)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,如下所示: 

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert("Clicked");
};

使用DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this 引用当前元素。来看一个例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(this.id); //"myBtn"
};

单击按钮显示的是元素的ID,这个ID 是通过this.id 取得的。不仅仅是ID,实际上可以在事件处理程序中通过this 访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。 

也可以删除通过DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null 即可: 

btn.onclick = null; //删除事件处理程序

将事件处理程序设置为null 之后,再单击按钮将不会有任何动作发生。

13.2.3 DOM2级事件处理程序

 “DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和emoveEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。 

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);

上面的代码为一个按钮添加了onclick 事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数是false)。与DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。 

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id);
}, false);
btn.addEventListener("click", function(){
    alert("Hello world!");
}, false);

这里为按钮添加了两个事件处理程序。这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的ID,其次会显示"Hello world!"消息。 

通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除,如下面的例子所示。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);
//这里省略了其他代码
btn.removeEventListener("click", function(){ //没有用!
  alert(this.id);
}, false);

在这个例子中,我们使用addEventListener()添加了一个事件处理程序。虽然调用removeEventListener()时看似使用了相同的参数,但实际上,第二个参数与传入addEventListener()中的那一个是完全不同的函数。而传入removeEventListener()中的事件处理程序函数必须与传入addEventListener()中的相同,如下面的例子所示。 

var btn = document.getElementById("myBtn");
var handler = function(){
  alert(this.id);
};
btn.addEventListener("click", handler, false);
//这里省略了其他代码
btn.removeEventListener("click", handler, false); //有效!

重写后的这个例子没有问题,是因为在addEventListener()和removeEventListener()中使用了相同的函数。 

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。

13.2.4 IE事件处理程序

IE 实现了与DOM 中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8 及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

要使用attachEvent()为按钮添加一个事件处理程序,可以使用以下代码。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});

注意,attachEvent()的第一个参数是"onclick",而非DOM 的addEventListener()方法中的"click"。

在IE 中使用attachEvent()与使用DOM0 级方法的主要区别在于事件处理程序的作用域。在使用DOM0 级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this 等于window。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert(this === window); //true
});

与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。来看下面的例子。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert("Clicked");
});
btn.attachEvent("onclick", function(){
  alert("Hello world!");
});

这里调用了两次attachEvent(),为同一个按钮添加了两个不同的事件处理程序。不过,与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是"Hello world!",然后才是"Clicked"。

使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。与DOM 方法一样,这也意味着添加的匿名函数将不能被移除。不过,只要能够将对相同函数的引用传给detachEvent(),就可以移除相应的事件处理程序。例如:

var btn = document.getElementById("myBtn");
var handler = function(){
  alert("Clicked");
};
btn.attachEvent("onclick", handler);
//这里省略了其他代码
btn.detachEvent("onclick", handler);

13.2.5 跨浏览器的事件处理程序

为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的JavaScript 库,还有一些开发人员会自己开发最合适的事件处理的方法。自己编写代码其实也不难,只要恰当地使用能力检测即可。要保证处理事件的代码能在大多数浏览器下一致地运行,只需关注冒泡阶段。

第一个要创建的方法是addHandler(),它的职责是视情况分别使用DOM0 级方法、DOM2 级方法或IE 方法来添加事件。这个方法属于一个名叫EventUtil 的对象,本书将使用这个对象来处理浏览器间的差异。addHandler()方法接受3 个参数:要操作的元素、事件名称和事件处理程序函数。

与addHandler()对应的方法是removeHandler(),它也接受相同的参数。这个方法的职责是移除之前添加的事件处理程序——无论该事件处理程序是采取什么方式添加到元素中的,如果其他方法无效,默认采用DOM0 级方法。

EventUtil 的用法如下所示。

var EventUtil = {
  addHandler: function(element, type, handler){
    if (element.addEventListener){
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent){
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },
  removeHandler: function(element, type, handler){
    if (element.removeEventListener){
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent){
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  }
};

这两个方法首先都会检测传入的元素中是否存在DOM2 级方法。如果存在DOM2 级方法,则使用该方法:传入事件类型、事件处理程序函数和第三个参数false(表示冒泡阶段)。如果存在的是IE 的方法,则采取第二种方案。注意,为了在IE8 及更早版本中运行,此时的事件类型必须加上"on"前缀。最后一种可能就是使用DOM0 级方法(在现代浏览器中,应该不会执行这里的代码)。此时,我们使用的是方括号语法来将属性名指定为事件处理程序,或者将属性设置为null。

可以像下面这样使用EventUtil 对象:

var btn = document.getElementById("myBtn");
var handler = function(){
  alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//这里省略了其他代码
EventUtil.removeHandler(btn, "click", handler);

13.3 事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持event 对象,但支持方式不同。

13.3.1 DOM中的事件对象

兼容DOM 的浏览器会将一个event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或DOM2 级),都会传入event 对象。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
  alert(event.type); //"click"
}, false);

这个例子中的两个事件处理程序都会弹出一个警告框,显示由event.type 属性表示的事件类型。这个属性始终都会包含被触发的事件类型, 例如"click" ( 与传入addEventListener() 和removeEventListener()中的事件类型一致)。

在通过HTML 特性指定事件处理程序时,变量event 中保存着event 对象。请看下面的例子。

<input type="button" value="Click Me" onclick="alert(event.type)"/>

以这种方式提供event 对象,可以让HTML 特性事件处理程序与JavaScript 函数执行相同的操作。

event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。

属性/方法 类型 读/写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消事件的默认行为
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读

为true 表示已经调用了preventDefault()(DOM3级事件中新增)

detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读

调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,
3表示冒泡阶段

preventDefault() Function 只读

取消事件的默认行为。如果cancelable是true,则可以使用这个方法

stopImmediatePropagation() Function 只读

取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用
(DOM3级事件中新增)

stopPropagation() Function 只读

取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法

target Element 只读 事件的目标
trusted Boolean 只读

为true表示事件是浏览器生成的。为false表示事件是由开发人员通过
JavaScript 创建的(DOM3级事件中新增)

type String 只读 被触发的事件的类型
view AbstractView 只读

与事件关联的抽象视图。等同于发生事件的window对象

在事件处理程序内部,对象this 始终等于currentTarget 的值,而target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget 和target 包含相同的值。来看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.currentTarget === this); //true
  alert(event.target === this); //true
};

这个例子检测了currentTarget 和target 与this 的值。由于click 事件的目标是按钮,因此这三个值是相等的。如果事件处理程序存在于按钮的父节点中(例如document.body),那么这些值是不相同的。再看下面的例子。

document.body.onclick = function(event){
  alert(event.currentTarget === document.body); //true
  alert(this === document.body); //true
  alert(event.target === document.getElementById("myBtn")); //true
};

当单击这个例子中的按钮时,this 和currentTarget 都等于document.body,因为事件处理程序是注册到这个元素上的。然而,target 元素却等于按钮元素,因为它是click 事件真正的目标。由于按钮上并没有注册事件处理程序,结果click 事件就冒泡到了document.body,在那里事件才得到了处理。

在需要通过一个函数处理多个事件时,可以使用type 属性。例如:

var btn = document.getElementById("myBtn");
var handler = function(event){
  switch(event.type){
    case "click":
      alert("Clicked");
      break;
    case "mouseover":
      event.target.style.backgroundColor = "red";
      break;
    case "mouseout":
      event.target.style.backgroundColor = "";
      break;
  }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn. onm ouseout = handler;

这个例子定义了一个名为handler 的函数,用于处理3 种事件:click、mouseover 和mouseout。当单击按钮时,会出现一个与前面例子中一样的警告框。当按钮移动到按钮上面时,背景颜色应该会变成红色,而当鼠标移动出按钮的范围时,背景颜色应该会恢复为默认值。这里通过检测event.type
属性,让函数能够确定发生了什么事件,并执行相应的操作。

要阻止特定事件的默认行为,可以使用preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到其href 特性指定的URL。如果你想阻止链接导航这一默认行为,那么通过链接的onclick 事件处理程序可以取消它,如下面的例子所示。

var link = document.getElementById("myLink");
link.onclick = function(event){
  event.preventDefault();
};

只有cancelable 属性设置为true 的事件,才可以使用preventDefault()来取消其默认行为。

另外,stopPropagation()方法用于立即停止事件在DOM 层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body 上面的事件处理程序,如下面的例子所示。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert("Clicked");
  event.stopPropagation();
};
document.body.onclick = function(event){
  alert("Body clicked");
};

对于这个例子而言,如果不调用stopPropagation(),就会在单击按钮时出现两个警告框。可是,由于click 事件根本不会传播到document.body,因此就不会触发注册在这个元素上的onclick 事件处理程序。

事件对象的eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么eventPhase 等于1;如果事件处理程序处于目标对象上,则eventPhase 等于2;如果是在冒泡阶段调用的事件处理程序,eventPhase 等于3。这里要注意的是,尽管
“处于目标”发生在冒泡阶段,但eventPhase 仍然一直等于2。来看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event){
  alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event){
  alert(event.eventPhase); //3
};

当单击这个例子中的按钮时,首先执行的事件处理程序是在捕获阶段触发的添加到document.body中的那一个,结果会弹出一个警告框显示表示eventPhase 的1。接着,会触发在按钮上注册的事件处理程序,此时的eventPhase 值为2。最后一个被触发的事件处理程序,是在冒泡阶段执行的添加到document.body 上的那一个,显示eventPhase 的值为3。而当eventPhase 等于2 时,this、target和currentTarget 始终都是相等的。

只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完成,event 对象就会被销毁。

13.3.2 IE中的事件对象

与访问DOM中的event 对象不同,要访问IE 中的event 对象有几种不同的方式,取决于指定事件处理程序的方法。在使用DOM0 级方法添加事件处理程序时,event 对象作为window 对象的一个属性存在。来看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  var event = window.event;
  alert(event.type); //"click"
};

在此,我们通过window.event 取得了event 对象,并检测了被触发事件的类型(IE 中的type属性与DOM中的type 属性是相同的)。可是,如果事件处理程序是使用attachEvent()添加的,那么就会有一个event 对象作为参数被传入事件处理程序函数中,如下所示。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
  alert(event.type); //"click"
});

在像这样使用attachEvent()的情况下,也可以通过window 对象来访问event 对象,就像使用DOM0 级方法时一样。不过为方便起见,同一个对象也会作为参数传递。

如果是通过HTML特性指定的事件处理程序,那么还可以通过一个名叫event 的变量来访问event对象(与DOM 中的事件模型相同)。再看一个例子。

<input type="button" value="Click Me" onclick="alert(event.type)">

IE 的event 对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的或者相关的DOM属性和方法。与DOM的event 对象一样,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都会包含下表所列的属性和方法。

属性/方法 类型 读/写 说明
cancelBubble Boolean 读/写

默认值为false,但将其设置为true就可以取消事件冒泡(与DOM中
的stopPropagation()方法的作用相同)

returnValue Boolean 读/写

默认值为true,但将其设置为false就可以取消事件的默认行为(与
DOM中的preventDefault()方法的作用相同)

srcElement Element 只读 事件的目标(与DOM中的target属性相同)
type String 只读 被触发的事件的类型

因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为this 会始终等于事件目标。故而,最好还是使用event.srcElement 比较保险。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick", function(event){
  alert(event.srcElement === this); //false
});

在第一个事件处理程序中(使用DOM0 级方法指定的),srcElement 属性等于this,但在第二个事件处理程序中,这两者的值不相同。

如前所述,returnValue 属性相当于DOM 中的preventDefault()方法,它们的作用都是取消给定事件的默认行为。只要将returnValue 设置为false,就可以阻止默认行为。来看下面的例子。

var link = document.getElementById("myLink");
link.onclick = function(){
  window.event.returnValue = false;
};

这个例子在onclick 事件处理程序中使用returnValue 达到了阻止链接默认行为的目的。与DOM不同的是,在此没有办法确定事件是否能被取消。

相应地,cancelBubble 属性与DOM 中的stopPropagation()方法作用相同,都是用来停止事件冒泡的。由于IE 不支持事件捕获,因而只能取消事件冒泡;但stopPropagatioin()可以同时取消事件捕获和冒泡。例如:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert("Clicked");
  window.event.cancelBubble = true;
};

document.body.onclick = function(){
  alert("Body clicked");
};

通过在onclick 事件处理程序中将cancelBubble 设置为true,就可阻止事件通过冒泡而触发document.body 中注册的事件处理程序。结果,在单击按钮之后,只会显示一个警告框。

13.3.3 跨浏览器的事件对象

跨浏览器的写法:

var EventUtil={
    addHandler:function(element,type,handler){
        if(element.addEventListener){
                element.addEventListener(type,handler,false);
        }else if(element.attachEvent){
            element.attachEvent("on"+type,handler);
        }else{
            element["on"+type]=handler;
        }
    },
    getEvent:function(event){
        return event?event:window.event;
    },
    getTarget:function(event){
        return event.target||event.srcElement;
    },
    preventDefault:function(event){
        if(event.preventDefault){
            event.preventDefault;
        }else{
            event.returnValue=false;
        }
    },
    removeHandler:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler,false);
        }else if(element.datachEvent){
            element.detachEvent("on"+type,handler);
        }else{
            element["on"+type]=null;
        }
    },
    stopPropagation:function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        }else{
            event.cancelBubble=true;
        }
    }
};

getEvent(),它返回对event对象的引用。考虑到IE 中事件对象的位置不同,可以使用这个方法来取得event 对象,而不必担心指定事件处理程序的方式。在使用这个方法时,必须假设有一个事件对象传入到事件处理程序中,而且要把该变量传给这个方法。

btn.onclick = function(event){
    event = EventUtil.getEvent(event);
};

在兼容DOM 的浏览器中,event 变量只是简单地传入和返回。而在IE 中,event 参数是未定义的(undefined),因此就会返回window.event。将这一行代码添加到事件处理程序的开头,就可以确保随时都能使用event 对象,而不必担心用户使用的是什么浏览器。

getTarget(),它返回事件的目标。在这个方法内部,会检测event 对象的target属性,如果存在则返回该属性的值;否则,返回srcElement 属性的值。可以像下面这样使用这个方法。

btn.onclick = function(event){
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
};

preventDefault(),用于取消事件的默认行为。在传入event 对象后,这个方法会检查是否存在preventDefault()方法,如果存在则调用该方法。如果preventDefault()方法不存在,则将returnValue 设置为false。下面是使用这个方法的例子。

var link = document.getElementById("myLink");
link.onclick = function(event){
  event = EventUtil.getEvent(event);
  EventUtil.preventDefault(event);
};

stopPropagation(),其实现方式类似。首先尝试使用DOM 方法阻止事件流,否则就使用cancelBubble 属性。下面看一个例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert("Clicked");
  event = EventUtil.getEvent(event);
  EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
  alert("Body clicked");
};

在此,首先使用EventUtil.getEvent()取得了event 对象,然后又将其传入到EventUtil.stopPropagation()。别忘了由于IE 不支持事件捕获,因此这个方法在跨浏览器的情况下,也只能用来阻止事件冒泡。

 

第13章 事件