JavaScript第十三章(1)

116 阅读12分钟

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

1.事件流

当浏览器发展到第四代时(IE4及 Netscape Communicator 4),浏览器开发团队遇到了一个很有意思 的问题:页面的哪一部分会拥有某个特定的事件?要明白这个问题问的是什么,可以想象画在一张纸上 的一组同心圆。如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的所有圆。两家 公司的浏览器开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,他们都认为单击事 件不仅仅发生在按钮上。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整 个页面。 事件流描述的是从页面中接收事件的顺序。但有意思的是,IE 和 Netscape 开发团队居然提出了差 不多是完全相反的事件流的概念。IE的事件流是事件冒泡流,而 Netscape Communicator的事件流是事 件捕获流。

1.1 事件冒泡

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

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

如果你单击了页面中的

元素,那么这个 click 事件会按照如下顺序传播: (1)
(2) (3) (4) document

层也就是说,click 事件首先在

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

1.2 事件捕获

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

元素就会以下列顺序触发 click 事件。 (1) document (2) (3) (4)

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

元素。

由于老版本的浏览器不支持,因此很少有人使用事件捕获。我们也建议读者放心地使用事件冒泡, 在有特殊需要时再使用事件捕获。

1.3 DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首 先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。后一个阶段是冒泡阶 段,可以在这个阶段对事件做出响应。

在 DOM事件流中,实际的目标(

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

2 事件处理程序

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

2.1 HTML事件处理程序

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

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

2.2 DOM0级事件处理程序

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

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

2.3 DOM2级事件处理程序

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

2.4 IE事件处理程序

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

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

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

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

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

3 事件对象

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

3.1 DOM中的事件对象

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

在通过 HTML特性指定事件处理程序时,变量 event 中保存着 event 对象。

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

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

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

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

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

3.2 IE中的事件对象

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

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

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

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

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

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

3.3 跨浏览器的事件对象

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

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

第二个方法是 getTarget(),它返回事件的目标。在这个方法内部,会检测 event 对象的 target 属性,如果存在则返回该属性的值;否则,返回 srcElement 属性的值

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

第四个方法是 stopPropagation(),其实现方式类似。首先尝试使用 DOM方法阻止事件流,否 则就使用 cancelBubble 属性。