3.3 事件传播

3.3 事件传播

在说明基于通常不可单击的页面元素处理单击事件的能力时,我们构思的界面中已经给出了一些提示——样式表切换器标签(即<h3>元素)实际上都是活动的,随时等待用户操作。为了改进界面,我们可以为按钮添加一种翻转状态,以便清楚地表明它们能与鼠标进行某种方式的交互:

1
2
3
4
.hover { 
cursor: pointer;
background-color: #afa;
}

CSS规范加入了一个名叫:hover的伪类选择符,这个选择符可以让样式表在用户鼠标指针悬停在某个元素上时,影响元素的外观。这个伪类选择符在某种程度上可以帮我们解决问题,但在这里,我们要介绍jQuery.hover方法。这个方法可以让我们在鼠标指针进入元素和离开元素时,通过JavaScript来改变元素的样式——事实上是可以执行任意操作。

同前面介绍的简单事件方法不同,.hover()方法接受两个函数参数。

  • 第一个函数会在鼠标指针进入被选择的元素时执行,
  • 第二个函数会在鼠标指针离开该元素时触发。

我们可以在这些时候修改应用到按钮上的类,从而实现翻转效果,参见代码清单3-10。

1
2
3
4
5
6
7
8
9
10
11
$(document).ready(function () {
$('#switcher h3').hover(
//鼠标进入时触发
function () {
$(this).addClass('hover');
},
// 鼠标离开时触发
function () {
$(this).removeClass('hover');
});
});

这里,我们再次使用隐式迭代和事件上下文实现了简洁的代码。现在,当鼠标指针悬停在<h3>上时,我们都能看到如图3-6中所示的应用了类之后的效果。

而且,使用.hover()也意味着可以避免JavaScript中的事件传播(event propagation)导致的头痛问题。要理解事件传播的含义,首先必须搞清楚JavaScript如何决定由哪个元素来处理给定的事件。

3.3.1 事件的旅程

当页面上发生一个事件时,每个层次上的DOM元素都有机会处理这个事件。以下面的页面模型为例:

1
2
3
4
5
6
7
8
9
10
<div class="foo"> 
<span class="bar">
<a href="http://www.example.com/">
The quick brown fox jumps over the lazy dog.
</a>
</span>
<p>
How razorback-jumping frogs can level six piqued gymnasts!
</p>
</div>

当在浏览器中形象化地呈现这些由嵌套的代码构成的元素时,我们看到的效果如图3-7所示。

从逻辑上看,任何事件都可能会有多个元素负责响应。举例来说,如果单击了页面中的链接元素,那么<div><span><a>全都应该得到响应这次单击的机会。毕竟,这3个元素同时都处于用户鼠标指针之下。而<p>元素则与这次交互操作无关。
允许多个元素响应单击事件的一种策略叫做事件捕获①。在事件捕获的过程中,事件首先会交给最外层的元素,接着再交给更具体的元素。在这个例子中,意味着单击事件首先会传递给<div>,然后是<span>,最后是<a>,如图3-8所示。
这里有一张图片
另一种相反的策略叫做事件冒泡即当事件发生时,会首先发送给最具体的元素,在这个元素获得响应机会之后,事件会向上冒泡到更一般的元素。在我们的例子中,<a>会首先处理事件,然后按照顺序依次是<span><div>,如图3-9所示。
这里有一张图片
毫不奇怪,不同的浏览器开发者最初采用的是不同的事件传播模型。因而,最终出台的DOM标准规定应该同时使用这两种策略:

  • 首先,事件要从一般元素到具体元素逐层捕获,
  • 然后,事件再通过冒泡返回DOM树的顶层。

而事件处理程序可以注册到这个过程中的任何一个阶段。
为了确保跨浏览器的一致性,而且也为了让人容易理解,jQuery始终会在模型的冒泡阶段注册事件处理程序。因此,我们总是可以假定最具体的元素会首先获得响应事件的机会

3.3.2 事件冒泡的副作用

事件冒泡可能会导致始料不及的行为,特别是在错误的元素响应mouseovermouseout事件的情况下。
假设在我们的例子中,为<div>添加了一个mouseout事件处理程序。
当用户的鼠标指针退出这个<div>时,会按照预期运行mouseout处理程序。因为这个过程发生在顶层元素上,所以其他元素不会取得这个事件。
但是,当指针从<a>元素上离开时,<a>元素也会取得一个mouseout事件。然后,这个事件会向上冒泡到<span><div>,从而触发上述的事件处理程序。这种冒泡序列很可能不是我们所希望的。

避免冒泡问题

mouseentermouseleave事件,无论是单独绑定,还是.hover()方法中组合绑定,都可以避免这些冒泡问题。在使用它们处理事件的时候,可以不用担心某些非目标元素得到mouseovermouseout事件导致的问题。
刚才介绍的mouseout的问题说明了限制事件作用域的必要性。虽然.hover()可以处理这种特殊情况,但在其他情况下,我们可能还需要从空间(阻止事件发送到某些元素)和时间(阻止事件在某些时间段发送)上限制某个事件。