1. 什么是事件传播?
事件传播(event propagation)是事件冒泡(event bubbling)和事件捕获(event capturing)的统称:
事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。在事件冒泡阶段只有标记为非捕获的监听器才会被调用。也就是那些调用 addEventListener() 时第三个参数为 false 时注册的监听器。默认值即为false。
事件捕获:不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件。在这一阶段,只有那些设置为捕获阶段工作的监听器才被调用。要为捕获阶段设置监听器,可以在调用 addEventListener 时设置第三个参数为 true:
demo
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件传播 demotitle>
head>
<body>
<div>
<button>
<span>点击span标签span>
button>
div>
<script>
const oP = document.querySelector('span');
const oB = document.querySelector('button');
const oD = document.querySelector('div');
const oBody = document.querySelector('body');
// -----捕获
oP.addEventListener('click',function(){
console.log('span标签被点击---捕获')
},true);
oB.addEventListener('click',function(){
console.log("button被点击---捕获")
},true);
oD.addEventListener('click', function(){
console.log('div被点击---捕获')
},true);
oBody.addEventListener('click',function(){
console.log('body被点击---捕获')
},true);
window.addEventListener('click',function(){
console.log('window被点击---捕获')
},true);
// -----冒泡
oP.addEventListener('click',function(){
console.log('span标签被点击---冒泡')
},false);
oB.addEventListener('click',function(){
console.log("button被点击---冒泡")
},false);
oD.addEventListener('click', function(){
console.log('div被点击---冒泡')
},false);
oBody.addEventListener('click',function(){
console.log('body被点击---冒泡')
},false);
window.addEventListener('click',function(){
console.log('window被点击---冒泡')
},false);
script>
body>
html>
CAPTURING_PHASE 捕获阶段
— 事件捕获:从window到事件目标的父级元素
AT_TARGET 目标阶段(当前事件源)
— 事件目标,在此阶段,将调用在事件目标上注册的所有监听器,而不管其捕获标志的值如何。
BUBBLING _PHASE :冒泡阶段
— 事件冒泡:从事件目标的父级元素回到 window
2.访问事件传播信息
每次触发DOM事件时会产生一个事件对象(也称event对象),此处的参数e接收事件对象。
3.停止事件传播
event.stopPropagation()
event.stopImmediatePropagation()
event.preventDefault()
4.事件监听执行顺序
问题:当前dom节点存在多个事件监听时,如何准确阻止事件传播?
总的来讲,同一dom事件监听可以分为两种情况:
1)为当前dom节点绑定同样的事件监听,如为div创建2个click事件监听:
DOM0级事件具有极好的跨浏览器优势,会以最快的速度绑定
<div id="box">点我</div>
<script>
// 某一个元素的同一个行为绑定不同的方法在script标签中后面的方法会覆盖前面的方法
var box = document.getElementById('box');
box.onclick = fun1;
box.onclick = fun2;
function fun1() {
console.log('方法1');
}
function fun2() {
console.log('方法2');
}
// 执行方法2;方法2覆盖方法1,所以方法1不执行
</script>
DOM2级事件是通过addEventListener绑定的事件,IE下的DOM2事件通过attachEvent绑定;可以给某一个元素的同一个行为绑定不同的方法在行内会分别执行
<div id="box">点我</div>
<script>
var box = document.getElementById('box');
box.addEventListener('click', fun1,false);
box.addEventListener('click', fun2,false);
function fun1() {
console.log('方法1');
}
function fun2() {
console.log('方法2');
}
// 执行方法1 // 执行方法2
</script>
2)为当前dom节点绑定不同的事件监听
mousedown: 当鼠标指针移动到元素上方,并按下鼠标按键(左、右键均可)时,会发生 mousedown 事件。
与 click 事件不同,mousedown 事件仅需要按键被按下,而不需要松开即可发生。
mouseup:当在元素上松开鼠标按键(左、右键均可)时,会发生 mouseup 事件。
与 click 事件不同,mouseup 事件仅需要松开按钮。当鼠标指针位于元素上方时,放松鼠标按钮就会触发该事件。
click:当鼠标指针停留在元素上方,然后按下并松开鼠标左键时,就会发生一次 click 事件。
注意:触发click事件的条件是按下并松开鼠标左键!,按下并松开鼠标右键并不会触发click事件。
blur:当元素失去焦点时触发blur事件;其为表单事件,blur和focus事件不会冒泡,其他表单事件都可以。
<div style="margin-top: 20px;">
<input type="text">
<button id="btn">button</button>
</div>
<script>
// mousedown->blur->mouseup->click
var btn = document.getElementById('btn');
var input = document.getElementsByTagName('input')[0];
btn.addEventListener('mousedown', function() {
console.log('>>>>>>>>>>>>>>>>>>> mousedown');
})
btn.addEventListener('mouseup', function() {
console.log('>>>>>>>>>>>>>>>>>>> mouseup');
})
btn.addEventListener('click', function() {
console.log('>>>>>>>>>>>>>>>>>>> click');
})
input.addEventListener('blur', function() {
console.log('>>>>>>>>>>>>>>>>>>> blur');
})
</script>
若在同一个元素上按下并松开鼠标左键,会依次触发mousedown、mouseup、click,前一个事件执行完毕才会执行下一个事件
若在同一个元素上按下并松开鼠标右键,会依次触发mousedown、mouseup,前一个事件执行完毕才会执行下一个事件,不会触发click事件
所以总结事件顺序应为:mousedown->(other)blur->mouseup->click
补充: js、jquery 中 blur事件和click事件冲突
问题说明:
在表单验证中,我们往往会在输入框失去焦点时触发一个blur事件,但当失去焦点后点击了一个按钮,这时blur事件和click事件就都触发了,由于js是单线程的所以就出现了问题,现在需要让blur先执行验证,然后在触发click事件。
解决方法:
给按钮的click事件设置延迟执行setTimeOut(fn,100),延迟时间的设置要大于blur事件的执行时间,这样就会在blur事件执行完后在执行click事件。或者用mousedown监听替代click。
5.事件代理(事件委托)
问题:不知道大家在平时的使用的时候有没有遇到过这样的一种情况,如果事件涉及到更新HTML节点或者添加HTML节点的时候,就会出现这样的一种情况,新添加的节点无法绑定事件,更新的节点也是无法绑定事件,表现的行为是无法触发事件
方案:由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
// 将父层元素 #list 下的 li 元素的事件委托到它的父层元素上:
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判断是否匹配目标元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is: ', target.innerHTML);
}
});
6.拓展
属性 | 描述 | DOM |
---|---|---|
bubbles | 返回布尔值,指示事件是否是起泡事件类型。 | 2 |
cancelable | 返回布尔值,指示事件是否可拥可取消的默认动作。 | 2 |
currentTarget | 返回其事件监听器触发该事件的元素。在事件程序中,this和currentTarget指代的是同一对象。 | 2 |
eventPhase | 返回事件传播的当前阶段。调用事件处理程序的阶段:1 捕获;2 处于阶段;3 冒泡阶段;这个属性的变化需要在断点中查看,不然你看到的总是0 | 2 |
target | 返回触发此事件的元素(事件的目标节点)。 | 2 |
timeStamp | 返回事件生成的日期和时间。 | 2 |
type | 返回当前 Event 对象表示的事件的名称。 | 2 |
view | 与事件关联的抽象视图,发生事件的window对象 | 2 |
preventDefault | 取消事件默认行为,cancelable是true时可以使用 | 2 |
stopPropagation | 取消事件捕获/冒泡,bubbles为true才能使用 | 2 |
stopImmediatePropagation | 取消事件进一步冒泡,并且组织任何事件处理程序被调用 | 3 |
事件冒泡 | 事件捕获 |
---|---|
IE和标准浏览器(ie8以下只支持事件冒泡,不支持事件捕获) | 标准浏览器 |
.addEventListener(‘click’,function(){},false) | .attachEvent(‘onclick’,function(){}) |
---|---|
三个参数: 1.事件类型,没有on 2.事件处理函数 3.布尔型的数值,默认false(事件冒泡),true(事件捕获) |
有两个参数: 1.事件类型,有on 2.事件处理函数 |
阻止事件冒泡/捕获 : e.stopPropagation(); | 阻止事件冒泡 : window.event,cancelBubble=true |
target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
type
表示监听事件类型的字符串。
listener
当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。
options 可选
一个指定有关 listener 属性的可选参数对象。可用的选项如下:
capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
useCapture 可选
Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。如果没有指定, useCapture 默认为 false 。