原文标题:Javascript - Event order 原文链接:https://www.quirksmode.org/js/events_order.html
Netscape 4 只支持事件捕获,Explorer只支持事件冒泡。Netscape 6和 Konqueror冒泡和捕获均支持,但Opera 和iCab冒泡和捕获均不支持。
在介绍事件的那篇文章(文章链接:https://www.quirksmode.org/js/introevents.html)中,我提了个看起来比较难以理解的问题:“假设一个元素及其祖先元素的事件句柄指向了同一事件,哪个先触发?”不出意料,这取决于浏览器。
这个问题其实很简单。假定一个父元素内有一个子元素:
-----------------------------------
| element1 |
| ------------------------- |
| |element2 | |
| ------------------------- |
| |
-----------------------------------
二者均有一个onClick事件句柄。如果用户点击了元素2,就会触发元素1和元素2的点击事件。可两个事件哪个先被触发呢?哪个事件句柄先执行呢?换句话说,事件顺序是怎样?
可以预见的是,很久以前Netscape和Microsoftde的做法就是截然不同的。
以上的两种做法完全背道而驰。Explorer只支持事件冒泡。Mozilla,Opera 7和Konqueror冒泡和捕获均支持。旧版本的Opera和iCab冒泡和捕获均不支持。
当你使用事件捕获时:
| |
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
元素1的事件句柄先被触发,元素2的事件句柄后被触发。
当你使用事件冒泡时:
/ \
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
元素2的事件句柄先被触发,元素1的事件句柄后被触发。
W3C明智地在争论中保持了中立。任何发生在W3C事件模型中的事件首先会被捕获,直到它到达目标元素才会冒泡。
| | / \
-----------------| |--| |-----------------
| element1 | | | | |
| -------------| |--| |----------- |
| |element2 \ / | | | |
| -------------------------------- |
| W3C event model |
------------------------------------------
Web开发者可以选择是否在捕获或冒泡阶段注册一个事件句柄。这可以通过在先进模型那篇有相应解释的addEventListener()方法实现。如果它的最后一个参数是true,事件句柄会为捕获阶段而设置,如果是false,事件句柄会为冒泡阶段而设置。
假设你这样做了
element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)
假如用户点击元素2,会发生以下情况:
顺序反过来就是
element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)
现在如果用户点击元素2,会发生以下情况:
在支持W3C DOM的浏览器中,一个传统的事件注册
element1.onclick = doSomething2;
被视为在冒泡阶段注册。
很少有web开发者自觉使用事件捕获或冒泡。现在的Web网页没有必要将一个冒泡事件与几个不同的事件句柄绑定。用户可能会对点击一次鼠标后发生多个动作感到困惑,而你通常会保持你的事件处理脚本彼此分离。当用户点击了一个元素,一个动作被触发,点击另一个元素就会触发另一个动作。
当然在未来这种情况也许会改变,能有向上兼容的模型当然更好。但现在事件捕获和冒泡的主要实际应用是默认功能的注册。
你首先需要理解事件捕获或冒泡总会发生。如果你为整个文档定义了一个普通的onclick事件句柄:
document.onclick = doSomething;
if (document.captureEvents) document.captureEvents(Event.CLICK);
文档中的任何点击事件都将冒泡到文档并触发那个事件句柄。仅当一个在它之前的事件处理脚本命令该事件停止冒泡,事件才不会冒泡到文档。
由于任何事件都要在文档上结束,因此默认事件句柄成为可能。假设你有下面这个页面:
------------------------------------
| document |
| --------------- ------------ |
| | element1 | | element2 | |
| --------------- ------------ |
| |
------------------------------------
element1.onclick = doSomething;
element2.onclick = doSomething;
document.onclick = defaultFunction;
现在假如用户点击元素1或元素2,doSomething()被执行。只要愿意,你可以终止事件的传递。如果你没有终止它,事件会冒泡到defaultFunction()。如果用户点击了其他地方,defaultFunction()也被执行。这在某些时候会很有用。
在拖拽脚本中设置文档宽度事件句柄很有必要。通常一个图层的mousedown事件会选中这一图层,并使它响应mousemove事件。尽管mousedown为了避免浏览器bug通常在这一图层注册,但其他的事件句柄一定是文档宽度。
记住浏览器法则第一条:任何事都可能发生,尤其是当你没有准备时。可能当用户大幅度地移动鼠标时脚本无法正常工作,导致鼠标不会在图层上出现。
所以在这种情况下冒泡是很有用的,因为在文档层面注册你的事件句柄能保证它们总会被执行。
但你经常想要停用所有的捕获和冒泡,因为这样函数间就不会彼此干扰。除此之外如果你的文档结构很复杂(有很多嵌套表格之类),你可以通过关闭冒泡来节省系统资源。浏览器必须查看事件目标的每一个祖先元素是否存在事件句柄。即使什么都没发现,搜索仍然会耗费不少时间。
在微软模式下你必须设置事件的cancleBubble属性的值为true。
window.event.cancelBubble = true
W3C模型中你必须调用stopPropagation()方法。
e.stopPropagation()
这会阻止冒泡阶段事件的传递。在跨浏览器时:
function doSomething(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
在浏览器中设置cancleBubble属性无法保证不会有负面效果。浏览器会创建属性。当然它并没有真正禁止冒泡,但任这种分配本身是安全的。
正如早先所见,拥有target或srcElement的事件包含了事件发生时对元素的一个引用。我们的例子是元素2,因为用户会点击它。
理解在冒泡和捕获阶段(或任意一个)目标不变是很重要的:它始终保持对元素2的引用。
但假设我们注册了以下这些事件句柄;
element1.onclick = doSomething;
element2.onclick = doSomething;
如果用户点击元素2,doSomething()会被执行两次。但你怎么知道是哪个HTML元素最近绑定了这个事件?target/srcElement没有给出线索,因为元素2是事件的源头,它们经常指向元素2。
为解决这个问题W3C增加了currentTarget属性。它包含了最近绑定了事件的元素的引用:这正是我们需要的。不幸的是,微软模式并没有一个与之相似的属性。
你可以使用this关键字,在例子中它指向事件绑定的那个HTML元素,就像currentTarget。
但当你使用微软事件注册模型时this关键字没有指向HTML元素。结合微软模型中一个与currentTarget类似的属性的缺点,这意味着如果你这样做的话:
element1.attachEvent('onclick',doSomething)
element2.attachEvent('onclick',doSomething)
你无法知道是哪个HTML元素最近绑定了事件。这是微软事件注册模型最严重的问题,也是我从不使用它的原因,哪怕是IE/WIN才有的应用我也不使用。
我希望微软可以尽快地添加一个类似currentTarget的属性—或者干脆遵从标准?Web开发者需要这个好消息。
如果你从头到尾看完了这篇文章,建议你应该继续看看鼠标事件(文章链接:https://www.quirksmode.org/js/events_mouse.html)。
本文转载自:众成翻译
崔庆才
静觅博客博主,《Python3网络爬虫开发实战》作者