3.3 事件传播
3.3 事件传播
在说明基于通常不可单击的页面元素处理单击事件的能力时,我们构思的界面中已经给出了一些提示——样式表切换器标签(即<h3>
元素)实际上都是活动的,随时等待用户操作。为了改进界面,我们可以为按钮添加一种翻转状态,以便清楚地表明它们能与鼠标进行某种方式的交互:
1 | .hover { |
CSS
规范加入了一个名叫:hover
的伪类选择符,这个选择符可以让样式表在用户鼠标指针悬停在某个元素上时,影响元素的外观。这个伪类选择符在某种程度上可以帮我们解决问题,但在这里,我们要介绍jQuery
的.hover
方法。这个方法可以让我们在鼠标指针进入元素和离开元素时,通过JavaScript
来改变元素的样式——事实上是可以执行任意操作。
同前面介绍的简单事件方法不同,.hover()
方法接受两个函数参数。
- 第一个函数会在鼠标指针进入被选择的元素时执行,
- 第二个函数会在鼠标指针离开该元素时触发。
我们可以在这些时候修改应用到按钮上的类,从而实现翻转效果,参见代码清单3-10。
1 | $(document).ready(function () { |
这里,我们再次使用隐式迭代和事件上下文实现了简洁的代码。现在,当鼠标指针悬停在<h3>
上时,我们都能看到如图3-6中所示的应用了类之后的效果。
而且,使用.hover()
也意味着可以避免JavaScript
中的事件传播(event propagation
)导致的头痛问题。要理解事件传播的含义,首先必须搞清楚JavaScript
如何决定由哪个元素来处理给定的事件。
3.3.1 事件的旅程
当页面上发生一个事件时,每个层次上的DOM
元素都有机会处理这个事件。以下面的页面模型为例:
1 | <div class="foo"> |
当在浏览器中形象化地呈现这些由嵌套的代码构成的元素时,我们看到的效果如图3-7所示。
从逻辑上看,任何事件都可能会有多个元素负责响应。举例来说,如果单击了页面中的链接元素,那么<div>
、<span>
和<a>
全都应该得到响应这次单击的机会。毕竟,这3个元素同时都处于用户鼠标指针之下。而<p>
元素则与这次交互操作无关。
允许多个元素响应单击事件的一种策略叫做事件捕获
①。在事件捕获的过程中,事件首先会交给最外层的元素
,接着再交给更具体的元素。在这个例子中,意味着单击事件首先会传递给<div>
,然后是<span>
,最后是<a>
,如图3-8所示。
另一种相反的策略叫做事件冒泡。即当事件发生时,会首先发送给最具体的元素,在这个元素获得响应机会之后,事件会向上冒泡到更一般的元素。在我们的例子中,<a>
会首先处理事件,然后按照顺序依次是<span>
和<div>
,如图3-9所示。
毫不奇怪,不同的浏览器开发者最初采用的是不同的事件传播模型。因而,最终出台的DOM
标准规定应该同时使用这两种策略:
- 首先,事件要从一般元素到具体元素逐层捕获,
- 然后,事件再通过冒泡返回
DOM
树的顶层。
而事件处理程序可以注册到这个过程中的任何一个阶段。
为了确保跨浏览器的一致性,而且也为了让人容易理解,jQuery
始终会在模型的冒泡阶段注册事件处理程序。因此,我们总是可以假定最具体的元素会首先获得响应事件的机会。
3.3.2 事件冒泡的副作用
事件冒泡可能会导致始料不及的行为,特别是在错误的元素响应mouseover
或mouseout
事件的情况下。
假设在我们的例子中,为<div>
添加了一个mouseout
事件处理程序。
当用户的鼠标指针退出这个<div>
时,会按照预期运行mouseout
处理程序。因为这个过程发生在顶层元素上,所以其他元素不会取得这个事件。
但是,当指针从<a>
元素上离开时,<a>
元素也会取得一个mouseout
事件。然后,这个事件会向上冒泡到<span>
和<div>
,从而触发上述的事件处理程序。这种冒泡序列很可能不是我们所希望的。
避免冒泡问题
而mouseenter
和mouseleave
事件,无论是单独绑定,还是在.hover()
方法中组合绑定,都可以避免
这些冒泡问题。在使用它们处理事件的时候,可以不用担心某些非目标元素得到mouseover
或mouseout
事件导致的问题。
刚才介绍的mouseout
的问题说明了限制事件作用域的必要性。虽然.hover
()可以处理这种特殊情况,但在其他情况下,我们可能还需要从空间(阻止事件发送到某些元素)和时间(阻止事件在某些时间段发送)上限制某个事件。