Javascript - 事件顺序

原文标题: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的做法就是截然不同的。

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

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

事件捕获

当你使用事件捕获时:

               | |
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

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

事件冒泡

当你使用事件冒泡时:

               / \
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

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

W3C模型

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

                 | |  / \
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

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

假设你这样做了

element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)

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

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

顺序反过来就是

element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)

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

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

兼容传统模型

在支持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通常在这一图层注册,但其他的事件句柄一定是文档宽度。

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

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

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

关闭这个功能

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

在微软模式下你必须设置事件的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网络爬虫开发实战》作者

原文发布于微信公众号 - 进击的Coder(FightingCoder)

原文发表时间:2018-03-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏落花落雨不落叶

block,inline,inline-block的区别

2558
来自专栏Java Web

初学Java Web(8)——过滤器和监听器

什么是过滤器 过滤器就是 Servlet 的高级特性之一,就是一个具有拦截/过滤功能的一个东西,在生活中过滤器可以是香烟滤嘴,滤纸,净水器,空气净化器等,在 W...

3637
来自专栏偏前端工程师的驿站

CSS魔法堂:display:none与visibility:hidden的恩怨情仇

 还记得面试时被问起"请说说display:none和visibility:hidden的区别"吗?是不是回答完display:none不占用原来的位置,而vi...

963
来自专栏HTML5学堂

关于行、块元素的讲解以及HTML5元素的分类

继上周我们讲解了所有常用的CSS选择器以及CSS选择器的优先级。到目前为止,你是不是觉得静态页面布局简单了很多,而不是单单使用类名选择器(虽然很好用)来操作了。...

3037
来自专栏前端说吧

css笔记 - 张鑫旭css课程笔记之 z-index 篇

 z-index可以决定哪个元素覆盖在哪个元素上边。(这个学过ps的,想象一下图层的概念,z-index就像是调整图层的上下顺序。)

531
来自专栏java一日一条

Jsoup代码解读之六-parser(下)

读Jsoup源码并非无聊,目的其实是为了将webmagic做的更好一点,毕竟parser也是爬虫的重要组成部分之一。读了代码后,收获也不少,对HTML的知识也更...

592
来自专栏从零开始学 Web 前端

从零开始学 Web 之 DOM(二)对样式的操作,获取元素的方式

984
来自专栏xingoo, 一个梦想做发明家的程序员

【前端开发系列】—— CSS3属性选择器总结

想想自己为什么要学CSS,作为一个开发过前端的人员来说,调试一个图片花了半天的时间,最后发现分隔符用错了,实在是一件很丢人的事情。因此,痛下决心来学习CSS,...

1837
来自专栏Python研发

HTML

一个完整的网页是由HTML(超文本标记语言),css(层叠样式表)JavaScript(动态脚本语言)三部分组成.

922
来自专栏企鹅号快讯

Python3爬取1024图片

分析 列表页面 首先进入1024的导航网站,随便点击一个地址进入选择图片区或者在网站地址后面添加,这就是1024网站的图片区,这个爬虫就是主要抓取这个区域的所有...

3619

扫码关注云+社区