当我们触发一个dom事件(e.g: click
),都会进行一次事件对象传播的过程,传播节点包括事件源和它上面的所有祖先节点
,用于告知这些节点一次事件的发生。
发送事件对象之前,需要先确定此次的传播路径
,这是一个有序的dom节点的列表,列表最后一个节点是事件源,列表的前面都是事件源的祖先节点。
确定路径后,一次传播会经过三个阶段:捕获阶段、目标阶段、冒泡阶段
。如果某个节点不支持某个阶段,或者传播已结束都不会触发相应的事件监听函数。
如果你在事件传播中的某个节点调用了evt.stopPropagation
,则会阻止后续的传播过程。
我们常讲的事件冒泡和事件捕捉就是上面的捕获阶段和冒泡阶段。
事件委托则是利用了事件冒泡的原理控制多个元素的事件处理,在下方通过实例讲解。
既然我们知道了事件传播的三个阶段,那每个dom元素的事件触发应该在那个阶段呢?这是在你注册事件的时候决定的,通过addEventListener
方法的第三个参数useCapture
,默认为false,则在冒泡阶段触发事件,设置为true则在捕获阶段触发,但是对于事件源
(触发事件的元素)来说,他只存在于目标阶段,所以不管你将useCapture
设置为true还是false,都是会触发事件函数的。
我们可以通过下面的实例来验证下。
<div id="parent">
<div id="child" class="child">点击div>
div>
<script>
const parent = document.querySelector('#parent')
const child = document.querySelector('#child')
parent.addEventListener('click', function (e) {
console.log('parent事件被触发,', this.id)
}, false)
child.addEventListener('click', function (e) {
console.log('child事件被触发,', this.id)
}, false)
script>
上面parent
元素设置了冒泡阶段进行事件处理,点击child
元素,打印结果:
child事件被触发, child
parent事件被触发, parent
child元素的事件处理先触发。即使你将child的useCapture
改为true,也是同样的结果,因为child元素是事件源,它只在目标阶段处理事件。
<div id="parent">
<div id="child" class="child">点击div>
div>
<script>
const parent = document.querySelector('#parent')
const child = document.querySelector('#child')
parent.addEventListener('click', function (e) {
console.log('parent事件被触发,', this.id)
}, true)
child.addEventListener('click', function (e) {
console.log('child事件被触发,', this.id)
})
script>
parent元素设置了捕获阶段触发,打印结果:
parent事件被触发, parent
child事件被触发, child
parent元素的事件处理先触发。没问题。
当有多个类似的元素需要绑定事件时,一个一个去绑定即浪费时间,又不利于性能,这时候就可以用到事件委托,给他们的一个共同父级元素添加一个事件函数去处理他们所有的事件,然后根据
evt.target
找到最终的事件源
<ul id="1ist">
<li>111li>
<li>222li>
<li>333li>
<li>444li>
ul>
<script type="text/javascript">
document.getElementById('1ist').addEventListener('click', function (e) {
e.target.innerHTML = "被点击";
});
script>
来分析下,事件源为某一个li元素
,点击时先经过捕获阶段:window -> ... -> ul
,ul元素默认设置了冒泡阶段进行事件处理,所以什么都不发生。
再来到目标阶段,事件源li元素
同样没设置事件处理。
最后到了冒泡阶段,ul -> ... -> window
,刚好ul元素
设置了冒泡阶段的事件处理函数,触发函数,然后通过事件对象e
的target属性找到事件源是某个li元素
,给他设置innerHTML。
最后展示下e.stopPropagation()的兼容写法(兼容IE)
function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
}