前端重新学习(16)DOM中的事件(上)

来源 《Javascript高级程序设计》 笔记

JavaScript与HTML之间的交互是通过事件实现的。

事件:文档或浏览器窗口中发生的一些特定的交互瞬间。

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

浏览器的事件系统相对比较复杂。尽管所有主要浏览器已经实现了“DOM2 级事件”,但这个规范 本身并没有涵盖所有事件类型。浏览器对象模型(BOM)也支持一些事件,这些事件与文档对象模型 (DOM)事件之间的关系并不十分清晰,因为 BOM事件长期没有规范可以遵循(HTML5后来给出了详 细的说明)。随着 DOM3级的出现,增强后的 DOM事件 API变得更加繁琐。使用事件有时相对简单, 有时则非常复杂,难易程度会因你的需求而不同。不过,有关事件的一些核心概念是一定要理解的。 

事件是将 JavaScript与网页联系在一起的主要方式。“DOM3级事件”规范和HTML5定义了常见的 大多数事件。即使有规范定义了基本事件,但很多浏览器仍然在规范之外实现了自己的专有事件,从而 为开发人员提供更多掌握用户交互的手段。有些专有事件与特定设备关联,例如移动 Safari 中的 orientationchange 事件就是特定关联 iOS设备的。 

在使用事件时,需要考虑如下一些内存与性能方面的问题。 

  • 有必要限制一个页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户 感觉页面反应不够灵敏。 
  • 建立在事件冒泡机制之上的事件委托技术,可以有效地减少事件处理程序的数量。 
  • 建议在浏览器卸载页面之前移除页面中的所有事件处理程序。 

可以使用 JavaScript在浏览器中模拟事件。“DOM2级事件”和“DOM3级事件”规范规定了模拟事 件的方法,为模拟各种有定义的事件提供了方便。此外,通过组合使用一些技术,还可以在某种程度上 模拟键盘事件。IE8及之前版本同样支持事件模拟,只不过模拟的过程有些差异。 事件是 JavaScript中重要的主题之一,深入理解事件的工作机制以及它们对性能的影响至关重要。 

事件学习的过程如下

目录

事件流

IE事件流(事件冒泡)

其他浏览器(事件捕获)

DOM事件流 

事件处理程序

HTML事件处理程序

DOM0级事件处理程序

DOM2级事件处理程序

 IE事件处理程序

跨浏览器事件处理程序

事件对象

DOM中的事件对象

IE中的事件对象

跨浏览器的事件对象

 



事件流

事件流:描述从页面接收事件的顺序。

事件流描述的是从页面中接收事件的顺序。但有意思的是,IE 和 Netscape 开发团队居然提出了差 不多是完全相反的事件流的概念。IE的事件流是事件冒泡流,而 Netscape Communicator的事件流是事 件捕获流。 


IE事件流(事件冒泡)

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id = "myDiv">Click Me</div>
</body>
</html>

点击click事件传播顺序: 
(1)< div> 
(2)< body> 
(3)< html> 
(4)< document>

 IE5.5 及更早版本中的事件冒 泡会跳过<html>元素(从<body>直接跳到 document)。IE9、Firefox、Chrome和 Safari则将事件一直 冒泡到 window 对象。 

所有现代浏览器都支持事件冒泡,在具体实现上还是有一些差别。

其他浏览器(事件捕获)

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 对象开始捕获事件的。 由于老版本的浏览器不支持,因此很少有人使用事件捕获。我们也建议读者放心地使用事件冒泡, 在有特殊需要时再使用事件捕获。 

DOM事件流 

“DOM2级事件”规定的事件流包括三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

首 先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。后一个阶段是冒泡阶 段,可以在这个阶段对事件做出响应。以前面简单的 HTML页面为例,单击<div>元素会按照图13-3所 示顺序触发事件。 

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

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

事件处理程序

定义:事件就是用户或浏览器自身执行的某种动作。诸如 click、load 和 mouseover,都是事件的名字。 而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。

事件处理程序的名字以"on"开头,因此 click 事件的事件处理程序就是 onclick,load 事件的事件处理程序就是 onload。为事件指定处理 程序的方式有好几种。 

  • HTML事件处理程序
  • DOM0级事件处理程序
  • DOM2级事件处理程序
  •  IE事件处理程序
  • 跨浏览器事件处理程序

HTML事件处理程序

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

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

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

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

调用脚本函数

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

事件处理程序中的代码在执行时,有权访问全局作用 域中的任何代码。

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

在这个例子中,单击按钮会显示文本框中的文本。值得注意的是,这里直接引用了 username 元素。
 不过,在 HTML 中指定事件处理程序有两个缺点。首先,存在一个时差问题。因为用户可能会在 HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。假设 showMessage()函数是在按钮下方、页面的底部定义的。如果用户在页面解 析 showMessage()函数之前就单击了按钮,就会引发错误。为此,很多 HTML事件处理程序都会被封 装在一个 try-catch 块中,以便错误不会浮出水面,如下面的例子所示: 

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

缺点

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

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

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

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 之后,再单击按钮将不会有任何动作发生。 

如果你使用 HTML指定事件处理程序,那么 onclick 属性的值就是一个包含着 在同名 HTML特性中指定的代码的函数。而将相应的属性设置为 null,也可以删除 以这种方式指定的事件处理程序

DOM2级事件处理程序

“DOM2级事件”定义了两个方法

用于处理指定事件处理程序的操作:

addEventListener() 

用于处理删除事件处理程序的操作:

 removeEventListener()

所有 DOM节点中都包含这两个方法,并且它们都接受 3个参数

要处 理的事件名     ||    作为事件处理程序的函数    ||    一个布尔值。

最后这个布尔值参数如果是 true,表示在捕获 阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序

要在按钮上为 click 事件添加事件处理程序,可以使用下列代码: 
 

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()添加了一个事件处理程序。虽然调用 remove- EventListener()时看似使用了相同的参数,但实际上,第二个参数与传入 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()中使 用了相同的函数。 

流行的先定义函数后关联事件如下

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

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

这个例子将保存在变量 handler 中的函数作为事件处理程序。因此,后面的 detachEvent()可以 使用相同的函数来移除事件处理程序。 

支持 IE事件处理程序的浏览器有 IE和 Opera。 

跨浏览器事件处理程序

为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的 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);  

 addHandler()和 removeHandler()没有考虑到所有的浏览器问题,例如在 IE中的作用域问题。 不过,使用它们添加和移除事件处理程序还是足够了。此外还要注意,DOM0 级对每个事件只支持一 个事件处理程序。好在,只支持 DOM0级的浏览器已经没有那么多了,因此这对你而言应该不是什么 问题。 

事件对象

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

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

在事件处理程序内部,对象 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.onmouseout = 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;如果事件处理程序处于目标对象上,则 event- Phase 等于 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 对象就会被销毁。 

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 对象一样,这些属性和方法也会因为事件类型的不同 而不同,但所有事件对象都会包含下表所列的属性和方法。 

因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为 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 中注册的事件处理程序。结果,在单击按钮之后,只会显示一个警告框。 

跨浏览器的事件对象

虽然 DOM和 IE中的 event 对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来。 IE 中 event 对象的全部信息和方法 DOM 对象中都有,只不过实现方式不一样。不过,这种对应关系 让实现两种事件模型之间的映射非常容易。可以对前面介绍的 EventUtil 对象加以增强,添加如下方 法以求同存异。 
 


 var EventUtil = {
       addHandler: function(element, 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){
         //省略的代码
     }, 
 
    stopPropagation: function(event){
         if (event.stopPropagation){
             event.stopPropagation();
         } else {
             event.cancelBubble = true;
         }
     } 
 
}; 

以上代码显示,我们为 EventUtil 添加了 4个新方法。第一个是 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);
 }; 

以上代码可以确保在所有浏览器中单击该链接都不会打开另一个页面。首先,使用 EventUtil. getEvent()取得 event 对象,然后将其传入到 EventUtil.preventDefault()以取消默认行为。 第四个方法是 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不支持事件捕获,因此这个方法在跨浏览器的情况下,也只能用 来阻止事件冒泡。 

 

猜你喜欢

转载自blog.csdn.net/qq_16546829/article/details/81774061