事件冒泡是你在学习javaScript旅途中遇到的一个术语,它涉及到当一个元素被另一个元素嵌套时调用事件处理的顺序,并且两个元素注册了同一个事件(例如,点击事件)。
但是事件冒泡仅仅是难题的一部分。它经常和事件捕获和事件传播一起被提及,并且对这三个概念有着很深的了解是学习javaScript事件必不可少的,例如,假如你想实现事件委托。
<ul>
<li><a href="..."><img src="..." alt=""></a>
<li><a href="..."><img src="..." alt=""></a>
...
<li><a href="..."><img src="..." alt=""></a>
</ul>
点击图像不仅为相应的IMG元素生成一个单击事件,但也为父元素,祖父元素等等生成点击事件, 一路通过所有元素的祖先元素,在window 对象之前终止。
在DOM术语中,图像是事件目标,点击来自最里面的元素。事件目标,加上它的祖先,从它的父元素到window 对象,在DOM树中形成一个分支。 例如,在图像库中,该分支将由节点组成:IMG,A,LI,UL,BODY,HTML,document,window。
还要注意那个分支确定是静态的,也就是说,它是在事件初始调度时建立的。在事件处理期间对树进行的改动将被忽略。
事件传播是双向的,从 window 到事件目标,然后再反转过来。这种传播可以分为三个阶段:
- 从 window 到事件目标的父级节点:这是 捕捉阶段
- 事件目标本身:这是 目标阶段
- 从事件目标的父级节点回到 window:冒泡阶段
通过调用监听器的类型可以区分这些阶段。
事件捕捉阶段
这个阶段只会调用 捕获者 监听器,就是说,那些监听器在注册的时候,addEventListener 的第三个参数使用了true
值:
el.addEventListener('click', listener, true)
如果这个参数缺省,其默认值是 false
,事件监听器也就不是捕获者。
因此,在这一阶段,只有从 window 到事件目标路径上的捕获者会被调用。
事件目标阶段
这个阶段所有注册到事件目标的监听器都会被调用,其捕获标志的值不管是什么都没关系。
事件冒泡阶段
事件冒泡阶段只有不是捕获者的监听器会被调用。也就是说,在调用 addEventListener()
注册这些监听器的时候,第三个参数是false
。
el.addEventListener('click', listener, false) // listener doesn't capture
el.addEventListener('click', listener) // listener doesn't capture
注意,在捕获阶段中所有事件如 focus
、blur
、load
等向下流向事件目标时,不会冒泡。这个过程会在 目标阶段后结束。
因此,到传播结束的时候,这个分支上所有监听器都确定会被调用一次。
事件冒泡不是在每一类事件上都会发生。在传播期间,监听器可以通过读取 event
对象的布尔属性 .bubbles
来获得某个事件是否会冒泡。
W3C UIEvents 规范中详细阐述了三个事件流阶段。
Copyright © 2016 万维网联盟 (MIT, ERCIM, Keio, Beihang).
访问传播信息
我提到过 event
对象的 .bubbles
属性。这个对象还提供了其它属性可以让监听器访问与传播相关的信息。
看看由 SitePoint (@SitePoint) 在 CodePen 上创建的 PenjmXdpz。
甚至可以在框外点击:这种情况下,事件目标会是 BODY
或 HTML
元素,这取决于点击在屏幕上的位置。
停止传播
事件传播可以由任何监听器通过调用事件对象的 stopPropagation 方法来停止。这就意味着传播路径上当前目标之后的所有节点上注册的监听器都不会被调用。相反,当前目标注册的监听器仍然会收到事件通知。
我们可以通过从之前的 Demo 派生出来的简单示例来检查这个行为,只需要在其中一个监听器中插入对stopPropagation()
的调用即可。现在我们已经在注册到 window 的监听器列表的最前面添加了一个新的捕获监听器:
window.addEventListener('click', e => { e.stopPropagation(); }, true);
window.addEventListener('click', listener('c1'), true);
window.addEventListener('click', listener('c2'), true);
window.addEventListener('click', listener('b1'));
window.addEventListener('click', listener('b2'));
这样,无论哪个盒子被点击,传播都会较早结束,只能到达 window
上注册的捕获监听器。
事件取消
某些事件与浏览器在传播结束时执行的默认操作相关联。 比如, 单击链接元素或单击表单提交按钮会导致浏览器导航到新页面,或分别提交表单。
可以避免在事件取消时执行这样的默认行为,通过在侦听器中调用事件对象e.preventDefault 方式阻止默认行为。
结论
这样,我希望能够向你展示事件冒泡和事件捕获在JavaScript中是如何工作。如果您有任何问题或意见,我很高兴在下面的讨论中听到这些问题和意见。