事件流
是描述从页面接受事件的顺序
事件分为三个阶段:
事件捕获阶段
目标事件阶段
事件冒泡阶段
事件冒泡是指事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点
事件捕获是指的不太具体的节点应该先接受到事件,而最具体的节点应该最后接受到事件。事件捕获的意义在于事件到达预定目标之前捕获它
实际上,目标在捕获阶段不会接受到事件
在使用案例说明之前,先说明几个对象:
event对象中有三个属性,在之后会被使用到:
1. eventPhase属性返回事件传播的当前阶段
常量 | 值 |
Event.CAPTURING_PHASE | 1 |
Event.AT_TARGET | 2 |
Event.BUBBLING_PHASE | 3 |
2. Target表示触发事件的节点
3. 事件当前节点
4. addEventListener的第三个参数是个布尔值,如果是true表示在事件捕获阶段调用事件处理程序;如果是false表示在事件冒泡阶段调用事件处理程序
第一种情况,不使用addEventListener事件,之间让元素捆绑onclick事件:
<body>
<div id="wrapDiv">wrapDiv
<p id="innerParent">innerParent
<span id="textSpan">textSpan</span>
</p>
</div>
</body>
var wrapDiv = document.getElementById("wrapDiv");
var innerP = document.getElementById("innerParent");
var textSpan = document.getElementById("textSpan");
wrapDiv.onclick = function(e){
console.log( "wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
innerP.onclick = function(e){
console.log( "innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
textSpan.onclick = function(e){
console.log( "textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
document.body.onclick = function(e){
console.log( "body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
document.onclick = function(e){
console.log( "document", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
window.onclick = function(e){
console.log( "window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
点击textSpan后控制台的输出:
说明了只有被点击元素是在目标阶段触发,而其他元素都是在冒泡阶段被触发。
第二种情况,使用addEventListener,并且将第三个参数置为false,发现结果和第一种情况是一样的:
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},false)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
第三种情况,在冒泡和捕获阶段都绑定监听
//冒泡阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},false)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
//捕获阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},true)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
根据控制台的输出可以看出,点击之后捕获阶段先发生,window最先捕获到->document->body->wrapdiv->innerP
textSpan目标阶段事件发生了两次,因为textSpan即在捕获阶段绑定了事件,又在冒泡阶段绑定了事件。
冒泡事件的过程与捕获相反
第四种情况,对目标事件在冒泡、捕获阶段都进行事件监听
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener true",e.eventPhase)
},true);
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener false",e.eventPhase)
}, false);
textSpan.onclick = function(e){
console.log("textSpan onclick",e.eventPhase)
};
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener false",e.eventPhase)
}, false);
textSpan.onclick = function(e){
console.log("textSpan onclick",e.eventPhase)
};
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener true",e.eventPhase)
},true);
根据两次运行的结果可以发现,textSpan作为被点击的目标元素,所有绑定在textSpan上的事件都会被触发,触发的顺序只和事件绑定顺序有关系
第四种情况,如果给父元素也添加onclick的事件监听,那么发生在哪个阶段呢?
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener false",e.eventPhase)
}, false);
wrapDiv.onclick = function(e){
console.log( "wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
textSpan.onclick = function(e){
console.log("textSpan onclick",e.eventPhase)
};
wrapDiv.onclick = function(e){
console.log( "wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
textSpan.addEventListener("click", function(e){
console.log("textSpan addEventListener true",e.eventPhase)
},true);
wrapDiv.onclick = function(e){
console.log( "wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}
wrap不是目标元素,所以它绑定的事件会遵循先发生捕获后冒泡的原则,上例子很明显,onclick事件发上在了冒泡阶段。而且虽然写了三次,但是只会被在冒泡阶段执行一次。如果点击wrap元素,也只会在目标阶段执行一次。
关于阻止冒泡事件
event.stopPropagation()
//冒泡阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},false)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
textSpan.addEventListener("click", function(e){
e.stopPropagation()
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
//捕获阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},true)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
点击目标元素textSpan后输入如下
跟第一种情况比较,可以发现事件捕获阶段到目标阶段后就结束了,冒泡阶段被阻止了。
stopPropagation()不仅可以在阻止事件的冒泡传播,还可以阻止事件的捕获传播
//冒泡阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},false)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
//捕获阶段
window.addEventListener('click',function (e) {
console.log("window", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
},true)
document.documentElement.addEventListener("click", function(e){
console.log("documentElement", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
document.body.addEventListener("click", function(e){
console.log("body", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
wrapDiv.addEventListener("click", function(e){
e.stopPropagation()
console.log("wrapDiv", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, true);
textSpan.addEventListener("click", function(e){
console.log("textSpan", e.target.nodeName, e.currentTarget.nodeName,e.eventPhase);
}, false);
点击textSpan事件后输出如下
最后附赠最近被面试问到的一个题目:
页面有元素A和B,B被包裹在A中,需要实现以下几个场景:
先触发A,在触发B
只触发A
只触发B
先触发B,在触发A
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#A{
width: 100px;
height: 100px;
background-color: pink;
position: relative;
}
#B{
width: 50px;
height: 50px;
background-color: deepskyblue;
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
</style>
</head>
<body>
<div id="A">
A
<div id="B">B</div>
</div>
</body>
<script>
var A = document.getElementById("A");
var B = document.getElementById("B");
//先触发A,在触发B
A.addEventListener('click',function () {
alert("A")
},true)//表示在捕获阶段执行
B.addEventListener('click',function () {
alert("B")
})
//只触发A
A.addEventListener('click',function (event) {
alert("A")
event.stopPropagation()//阻止事件向下传递
},true)//表示在捕获阶段执行
B.addEventListener('click',function () {
alert("B")
})
//只触发B
A.addEventListener('click',function (event) {
alert("A")
})
B.addEventListener('click',function () {
alert("B")
event.stopPropagation()//阻止事件冒泡
})
//先触发B,在触发A
A.addEventListener('click',function () {
alert("A")
})//事件在冒泡阶段执行
B.addEventListener('click',function () {
alert("B")
})
</script>
</html>