这个在面试中是一个问的比较多的问题,有时候也会在笔试的简答题中碰到。
首先我们要知道:一个完整的js事件流是从window开始,最后回到window的过程,如下图所示:
1-5是捕获过程,5-6是目标阶段,6-10是冒泡阶段;
以一段具体的代码来看:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
#wrapDiv, #innerP, #textSpan{
margin: 5px;padding: 5px;box-sizing: border-box;cursor: default;
}
#wrapDiv{
width: 300px;height: 300px;border: indianred 3px solid;
}
#innerP{
width: 200px;height: 200px;border: hotpink 3px solid;
}
#textSpan{
display: block;width: 100px;height: 100px;border: orange 3px solid;
}
</style>
</head>
<body>
<div id="wrapDiv">wrapDiv
<p id="innerP">innerP
<span id="textSpan">textSpan</span>
</p>
</div>
<script>
var wrapDiv = document.getElementById("wrapDiv");
var innerP = document.getElementById("innerP");
var textSpan = document.getElementById("textSpan");
// 捕获阶段绑定事件
window.addEventListener("click", function(e){
console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.addEventListener("click", function(e){
console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.body.addEventListener("click", function(e){
console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
textSpan.addEventListener("click", function(e){
console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
// 冒泡阶段绑定的事件
window.addEventListener("click", function(e){
console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.addEventListener("click", function(e){
console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.body.addEventListener("click", function(e){
console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>
</body>
</html>
- 事件捕获:首先window会获捕获到事件,之后document、documentElement、body会捕获到,再之后就是在body中DOM元素一层一层的捕获到事件,有wrapDiv、innerP;
- 目标阶段:真正点击的元素textSpan的事件发生了两次,因为在上面的JavaScript代码中,textSapn既在捕获阶段绑定了事件,又在冒泡阶段绑定了事件,所以发生了两次;
- 事件冒泡:和捕获阶段相反的将事件一步一步地冒泡到window;
补充:事件代理(事件委托)
要求:鼠标放到li上li的背景颜色变为灰色;
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
<li>item5</li>
<li>item6</li>
</ul>
不使用事件代理:
$("li").on("mouseover",function(){
$(this).css("background-color","#ddd").siblings().css("background-color","white");
})
使用事件代理:
$("ul").on("mouseover",function(e){
$(e.target).css("background-color","#ddd").siblings().css("background-color","white");
})
看上去并没有什么区别,但是使用事件代理的方法少了遍历所有li节点的操作,性能上肯定更加优化,而且如果后期又动态的增加li节点,不使用事件代理的话还需要再重新给新增的li节点绑定事件,但是使用事件代理的话则不需要了。