前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Javascript - 事件顺序

Javascript - 事件顺序

作者头像
崔庆才
发布2018-06-25 11:40:15
1K0
发布2018-06-25 11:40:15
举报
文章被收录于专栏:进击的Coder

原文标题: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)中,我提了个看起来比较难以理解的问题:“假设一个元素及其祖先元素的事件句柄指向了同一事件,哪个先触发?”不出意料,这取决于浏览器

这个问题其实很简单。假定一个父元素内有一个子元素:

代码语言:javascript
复制
-----------------------------------
| element1                        |
|   -------------------------     |
|   |element2               |     |
|   -------------------------     |
|                                 |
-----------------------------------

二者均有一个onClick事件句柄。如果用户点击了元素2,就会触发元素1和元素2的点击事件。可两个事件哪个先被触发呢?哪个事件句柄先执行呢?换句话说,事件顺序是怎样?

两种模型

可以预见的是,很久以前Netscape和Microsoftde的做法就是截然不同的。

  • Netscape指定元素1的事件先发生,称之为事件_捕获_。
  • Microsoft表示元素2的事件先发生,称之为事件_冒泡_。

以上的两种做法完全背道而驰。Explorer只支持事件冒泡。Mozilla,Opera 7和Konqueror冒泡和捕获均支持。旧版本的Opera和iCab冒泡和捕获均不支持。

事件捕获

当你使用事件捕获时:

代码语言:javascript
复制
               | |
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

元素1的事件句柄先被触发,元素2的事件句柄后被触发。

事件冒泡

当你使用事件冒泡时:

代码语言:javascript
复制
               / \
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

元素2的事件句柄先被触发,元素1的事件句柄后被触发。

W3C模型

W3C明智地在争论中保持了中立。任何发生在W3C事件模型中的事件首先会被捕获,直到它到达目标元素才会冒泡。

代码语言:javascript
复制
                 | |  / \
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

Web开发者可以选择是否在捕获或冒泡阶段注册一个事件句柄。这可以通过在先进模型那篇有相应解释的addEventListener()方法实现。如果它的最后一个参数是true,事件句柄会为捕获阶段而设置,如果是false,事件句柄会为冒泡阶段而设置。

假设你这样做了

代码语言:javascript
复制
element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)

假如用户点击元素2,会发生以下情况:

  1. 点击事件发生在捕获阶段。事件看起来好像元素2的任何祖先元素都有对应于捕获阶段的onclick事件句柄。
  2. 元素1上绑定的doSomething2()事件被执行。
  3. 事件传递到目标,没有发现任何一个对应捕获阶段的事件句柄。事件移向冒泡阶段并执行在冒泡阶段为元素2注册的doSomething()。
  4. 事件又一次向上传递并检查目标的任何祖先元素是否有对应冒泡阶段的事件句柄。最后没有发现任何句柄,因此什么也没发生。

顺序反过来就是

代码语言:javascript
复制
element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)

现在如果用户点击元素2,会发生以下情况:

  1. 点击事件发生于捕获阶段。事件会查看元素2的任何祖先元素是否存在对于捕获阶段的onclick事件句柄,但没有发现。
  2. 事件传递到目标。事件移动到自己的冒泡阶段并执行为元素2注册的对应冒泡阶段的doSomething()。
  3. 事件再次向上移动并检查目标的任何祖先元素是否有对应冒泡阶段的事件句柄。
  4. 事件在元素1上发现了事件句柄。于是doSomething2()被执行。

兼容传统模型

在支持W3C DOM的浏览器中,一个传统的事件注册

代码语言:javascript
复制
element1.onclick = doSomething2;

被视为在冒泡阶段注册。

事件冒泡的使用

很少有web开发者自觉使用事件捕获或冒泡。现在的Web网页没有必要将一个冒泡事件与几个不同的事件句柄绑定。用户可能会对点击一次鼠标后发生多个动作感到困惑,而你通常会保持你的事件处理脚本彼此分离。当用户点击了一个元素,一个动作被触发,点击另一个元素就会触发另一个动作。

当然在未来这种情况也许会改变,能有向上兼容的模型当然更好。但现在事件捕获和冒泡的主要实际应用是默认功能的注册

这总会发生

你首先需要理解事件捕获或冒泡总会发生。如果你为整个文档定义了一个普通的onclick事件句柄:

代码语言:javascript
复制
document.onclick = doSomething;
if (document.captureEvents) document.captureEvents(Event.CLICK);

文档中的任何点击事件都将冒泡到文档并触发那个事件句柄。仅当一个在它之前的事件处理脚本命令该事件停止冒泡,事件才不会冒泡到文档。

使用

由于任何事件都要在文档上结束,因此默认事件句柄成为可能。假设你有下面这个页面:

代码语言:javascript
复制
------------------------------------
| document                         |
|   ---------------  ------------  |
|   | element1    |  | element2 |  |
|   ---------------  ------------  |
|                                  |
------------------------------------

element1.onclick = doSomething;
element2.onclick = doSomething;
document.onclick = defaultFunction;

现在假如用户点击元素1或元素2,doSomething()被执行。只要愿意,你可以终止事件的传递。如果你没有终止它,事件会冒泡到defaultFunction()。如果用户点击了其他地方,defaultFunction()也被执行。这在某些时候会很有用。

在拖拽脚本中设置文档宽度事件句柄很有必要。通常一个图层的mousedown事件会选中这一图层,并使它响应mousemove事件。尽管mousedown为了避免浏览器bug通常在这一图层注册,但其他的事件句柄一定是文档宽度。

记住浏览器法则第一条:任何事都可能发生,尤其是当你没有准备时。可能当用户大幅度地移动鼠标时脚本无法正常工作,导致鼠标不会在图层上出现。

  • 如果onmousemove事件句柄注册给了图层,图层就不会对鼠标移动做出反应,这会让人困惑。
  • 如果onmouseup事件句柄在图层上被注册,事件就不会被捕获。所以图层会保持对鼠标的反应,甚至当用户以为自己放下图层后仍会保持反应。

所以在这种情况下冒泡是很有用的,因为在文档层面注册你的事件句柄能保证它们总会被执行。

关闭这个功能

但你经常想要停用所有的捕获和冒泡,因为这样函数间就不会彼此干扰。除此之外如果你的文档结构很复杂(有很多嵌套表格之类),你可以通过关闭冒泡来节省系统资源。浏览器必须查看事件目标的每一个祖先元素是否存在事件句柄。即使什么都没发现,搜索仍然会耗费不少时间。

在微软模式下你必须设置事件的cancleBubble属性的值为true。

代码语言:javascript
复制
window.event.cancelBubble = true

W3C模型中你必须调用stopPropagation()方法。

代码语言:javascript
复制
e.stopPropagation()

这会阻止冒泡阶段事件的传递。在跨浏览器时:

代码语言:javascript
复制
function doSomething(e)
{
  if (!e) var e = window.event;
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
}

在浏览器中设置cancleBubble属性无法保证不会有负面效果。浏览器会创建属性。当然它并没有真正禁止冒泡,但任这种分配本身是安全的。

当前目标

正如早先所见,拥有target或srcElement的事件包含了事件发生时对元素的一个引用。我们的例子是元素2,因为用户会点击它。

理解在冒泡和捕获阶段(或任意一个)目标不变是很重要的:它始终保持对元素2的引用。

但假设我们注册了以下这些事件句柄;

代码语言:javascript
复制
element1.onclick = doSomething;
element2.onclick = doSomething;

如果用户点击元素2,doSomething()会被执行两次。但你怎么知道是哪个HTML元素最近绑定了这个事件?target/srcElement没有给出线索,因为元素2是事件的源头,它们经常指向元素2。

为解决这个问题W3C增加了currentTarget属性。它包含了最近绑定了事件的元素的引用:这正是我们需要的。不幸的是,微软模式并没有一个与之相似的属性。

你可以使用this关键字,在例子中它指向事件绑定的那个HTML元素,就像currentTarget。

微软模型的问题

但当你使用微软事件注册模型时this关键字没有指向HTML元素。结合微软模型中一个与currentTarget类似的属性的缺点,这意味着如果你这样做的话:

代码语言:javascript
复制
element1.attachEvent('onclick',doSomething)
element2.attachEvent('onclick',doSomething)

你无法知道是哪个HTML元素最近绑定了事件。这是微软事件注册模型最严重的问题,也是我从不使用它的原因,哪怕是IE/WIN才有的应用我也不使用。

我希望微软可以尽快地添加一个类似currentTarget的属性—或者干脆遵从标准?Web开发者需要这个好消息。

尾声

如果你从头到尾看完了这篇文章,建议你应该继续看看鼠标事件(文章链接:https://www.quirksmode.org/js/events_mouse.html)。

本文转载自:众成翻译

崔庆才

静觅博客博主,《Python3网络爬虫开发实战》作者

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-03-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 进击的Coder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 两种模型
  • 事件捕获
  • 事件冒泡
  • W3C模型
  • 兼容传统模型
  • 事件冒泡的使用
  • 这总会发生
  • 使用
  • 关闭这个功能
  • 当前目标
  • 微软模型的问题
  • 尾声
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档