JavaScript中的冒泡与捕获
首先来看一个例子来明白什么是冒泡和捕获,来看下面的一个html结构
|
就是一个大盒子里面套着一个小盒子,为两个盒子设置了背景颜色以作区分,如下
现在为二者都添加一个点击方法
<script> |
现在如果点击里面的盒子inner
,那么outer
的点击事件会不会触发,因为按道理也算是点击了outer
的区域,所以outer
的点击事件应该被触发。现在问题又来了,是先触发inner
还是先触发outer
的点击事件呢? 按照二者触发顺序的不同分为捕获和冒泡。
现在点击绿色的小盒子,看看输出是什么
当我们点击里面的盒子即 inner
时,触发了它的点击事件,随后触发了 outer
的点击事件,这样触发子元素事件之后触发父元素事件的行为就叫做冒泡;捕获就是随之相反了,先处理 outer
,然后处理 inner
的事件。
要实现捕获的效果,首先我们为 addEventListener
方法的第三个参数设置为 true
,如下
outer.addEventListener('click', (e) => { |
这时我们在点击里面的盒子
这时是outer
的点击事件先被执行,然后是inner
的点击事件被执行。
冒泡和捕获的出现是因为以前的两大浏览器厂商 Netscape
和 Microsoft
对事件模型处理方法,Microsoft
采取的从目标元素(比如点击 inner
,inner
就是目标元素)开始,按 DOM
树向上冒泡;而 Netscape
采取的是相反的原则,即从顶部元素开始,直到事件目标元素。通过上面的例子可以知道,可以通过设置 addEventListener
方法的第三个参数可以设置是冒泡还是捕获,当设置为 true
时,是捕获,当设置为 false
时,是冒泡,默认是 false
。
现在考虑一个比较复杂的 DOM
结构,如下
|
上面四个盒子套在一起,我们为one
和three
设定为捕获模式,为two
和four
设定为冒泡模式,如果我们点击four
,这时的输出会是什么呢?
我们观察到输出的顺序为one -> three -> four -> two
,代码是怎么执行的呢? 首先事件处理器会从顶部开始即 one
(严格的说是从 window
),一直到目标元素,在这个路径中,如果遇到设置为捕获模式的则执行,碰到冒泡模式的则跳过,达到目标元素后,开始转换为冒泡模式,向上冒泡到one
,在这个路径中,如果碰到设置为冒泡模式的则执行,否则跳过。
现在我们来看看上面的执行流程:
- 首先从
one
开始向下捕获,one
设置为捕获模式,执行 - 遇到
two
,two
设置为冒泡模式,不执行跳过 - 遇到
three
,three
设置为捕获模式,执行 - 遇到
four
,到达目标元素,执行(此时不管four是冒泡还是捕获都没有关系,都会执行的) - 接着转变为冒泡模式,遇到
three
,three
为捕获模式,跳过 - 遇到
two
,two
为冒泡模式,执行 - 遇到
one
,one
为捕获模式,不执行,此时已经到达顶部,结束
通过上面的流程,不难知道输出的顺序为什么是one -> three -> four -> two
。
在父元素上代理事件
我们来看一个运用冒泡的小例子,假设有这个一个DOM结构
<ul> |
我们希望当点击 li
标签时,将 li
标签里面的文字变为红色,所以很有可能你会写出这样的代码
document.querySelectorAll("ul li").forEach(item => { |
这样写当然能达到效果,但是如果 ul
下面有成千上万个 li
,这样写未免性能太低,我们可以利用冒泡的特性,为 ul
绑定点击事件,如下
document.querySelector('ul').addEventListener('click', (e) => { |
这样不管 ul
下面有多少个 li
都没有关系。
解释:
这里可能有人不太懂
e.target
是什么,e.target
是指触发点击事件的元素,而不是ul
,因为我们点击的是li
标签,所以这里的e.target
是被点击的li
元素。如果想在addEventListener
里面访问ul
元素,可以使用this
。
阻止事件冒泡
有的时候我们不希望事件有冒泡操作,我们可以通过 event
对象的 stopPropagation
方法来阻止事件冒泡,以文章开头的 inner
和 outer
为例(outer
和 inner
都设置为冒泡模式),我们给 inner
的 addEventListener
修改为
inner.addEventListener('click', (e) => { |
这时我们点击inner
,这时只有inner
的点击事件被执行了