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