定义
DOM结构是一个树型结构,当一个HTML元素触发一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为 DOM 事件流(DOM event flow )。
事件流分为三个阶段
- 捕获阶段:事件从Document节点自上而下向目标节点传播的阶段;
- 目标阶段:真正的目标节点正在处理事件的阶段;
- 冒泡阶段:事件从目标节点自上而下向Document节点传播的阶段。
事件绑定/监听的方法
1. 直接绑定
直接在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等事件
x.onclick = function(){
console.log(‘1’)
}
x.onclick = function(){
console.log(‘2’)
}
//1不会打印出来,会被后面的2给覆盖
这种方法最简单,也是DOM level0最早支持的一种方法。但是这个方法存在一个很大的问题。那就是如果一个元素绑定事件时,有可能覆盖掉前面已经绑定好的事件!尤其是存在多个js文件时。为了解决这个问题,level2新增了事件监听
2. 事件监听
事件监听实现的功能和直接绑定差不多,但是新增了一个特点。那就是无论监听次,都不会覆盖掉前面的监听事件。本质原因是监听事件每次都会生产一个全新的匿名函数,和前面的函数完全不同,自然不会覆盖。
x.addEventListener(‘click’,function(){
console.log(1)
})
x.addEventListener(‘click’,function(){
console.log(2)
})
//1和2都会被打印出来,不会被覆盖掉
事件冒泡和捕获
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。
阻止冒泡
如果想阻止事件冒泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。
document.getElementById(‘div’).addEventListener(‘click’,function(e){
e.stopPropagation();
})
通过改变addEventListener的第三个参数改变事件的执行顺序。(false为冒泡阶段执行,true为捕获阶段执行,默认为false)
事件委托
我们实现事件委托就是基于上面几个理论。
现在,我们先设想一种情况,ul标签中的每个li标签都要绑定一个点击事件
<ul id="ul">
<li id="l1">1</li>
<li id="l2">2</li>
<li id="l3">3</li>
<li id="l4">4</li>
</ul>
l1.addEventListener('click', function() {})
l2.addEventListener('click', function() {})
l3.addEventListener('click', function() {})
l4.addEventListener('click', function() {})
但如果执行的函数都一样,且li个数很多,这就显得非常麻烦了。尤其是li个数不确定的时候(JavaScript动态生成),这种方式更是不适用。
聪明的你可能已经想到,我点击了li,这个时候不也等于点击了ul吗?那我直接把点击事件绑定在ul上不就好了?
var ul = document.getElementById('ul')
ul.addEventListener('click', function() {})
我们点击li,确实是实现了我们想要的功能。但又有一个新的问题。那就是ul如果有padding可能就会出bug。我们点击li之外,ul之内时,事件也触发了!这种结果肯定不是我们想要的。
那我触发事件之前先判断一下点击的目标不就好了?如果点的是li就触发,不然就不触发。
ul.addEventListener('click', function(e) {
// 检查事件源e.targe是否为Li
if (e.target && e.target.nodeName.toUpperCase == "LI") {
// 真正的处理过程在这里
console.log("点击成功");
}
}
再次测试函数,发现基本没有问题了。点击li触发事件,li之外不触发事件。这也是大多数人实现事件委托的方法,但却不是一个好的实现方法。因为这种方法也存在着明显的bug
<ul id="ul">
<li id="l1"><span>1</span></li>
<li id="l2">2</li>
<li id="l3">3</li>
<li id="l4">4</li>
</ul>
如果第一个li外面套了一个span的情况呢?我们再次点击第一个li,它!不!触!发!事!件!我们console.log出这个时候点击的标签,发现是span,自然不触发事件。显然我们之前写的事件委托有bug。有可能我们点击li的时候,li还有后代元素。这时候我们就应该先判断点击的元素的祖先元素当中有没有li,如果有li,那点击的还是li,如果没有,那就是真的没有了。最后写出的事件委托函数如下
ul.addEventListener('click', function() {
let el = e.target
while (el && !el.matches(selector)) {
el = el.parentNode
if (element === el) {
el = null
}
}
if (el) {
console.log('执行回调函数')
}
}
网友评论