在介绍这两个流程时,首先要了解一下js很重要的概念:事件流
事件流
事件流描述的是从页面中接受事件的顺序;
通俗的理解就是把浏览器抽象成一个内置台阶的容器,我们触发事件的操作抽象成水流;
当我们触发事件的时候👇 可以这样理解
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
假如我们需要触发id为div3的元素上的点击事件
那么就要从浏览器顶级对象window->DOM根元素document->html->body->div1->div2->div3然后进行触发事件的操作过程,可以暂时这样理解。
这其中涉及了浏览器事件模型和其中的一些操作,会在下文中详细讲解;
如果对浏览器事件模型有了解的读者可以直接去看如何阻止事件冒泡和事件捕获了^_^;
事件模型
了解了事件流的概念,那么就可以介绍事件模型了概念了,大体分为三种事件模型,分别为DOM0、IE,DOM2;
DOM0事件模型
首先,在DOM0中并没有事件流的概念,只有事件处理过程,也就是我点击某一个元素,如果元素上绑定了对应的操作的监听器,那么就对应的触发监听器的回调;如果没有绑定,就不会触发任何东西;
也就是说,在DOM0中是没有事件捕获和时间冒泡的,点击哪一个元素就触发哪一个回调,不会进行事件传播;
IE事件模型
IE事件模型相较于DOM0事件模型,新增了事件冒泡的概念,也就是在触发目标元素的回调后,会进行事件的传播,一层一层的传递到其祖先元素之上,查看是否有相应的事件处理函数,如果有,也会将祖先元素的事件处理回调触发;
下面看一个栗子👇
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
var div1 = document.getElementById("div1")
var div2 = document.getElementById("div2")
var div3 = document.getElementById("div3")
div1.addEventListener("click", function (e) {
console.log("div1 触发事件");
})
div2.addEventListener("click", function (e) {
console.log("div2 触发事件");
})
div3.addEventListener("click", function (e) {
console.log("div3 触发事件");
})
</script>
如上述代码,将所有的div元素都绑定上了click事件监听,如果现在点击div3,在DOM0事件模型中,就只会输出"div3 触发事件",但是在开启事件冒泡的情况下,输出如下:
首先触发了div3的绑定事件,之后一层层向其祖先元素传递,那么div1和div2上也绑定了点击的监听事件,那么他们也被触发了;
可以把冒泡理解为从内而外的触发每一个绑定了事件的回调
DOM2事件模型
DOM2事件模型相较于IE事件模型新增了事件捕获过程,如果读者对于我上面描述的事件冒泡理解的话,事件捕获其实就很好理解了,事件捕获和事件冒泡是完全相反的两个过程,如果说事件冒泡是从内而外的触发目标元素对应回调的过程,那么事件捕获就是从外向内的触发目标元素对应事件回调的过程;
上面一大串看起来很烦,那么用代码理解一下👇
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
var div1 = document.getElementById("div1")
var div2 = document.getElementById("div2")
var div3 = document.getElementById("div3")
div1.addEventListener("click", function (e) {
console.log("div1 触发事件");
},true)
div2.addEventListener("click", function (e) {
console.log("div2 触发事件");
}, true)
div3.addEventListener("click", function (e) {
console.log("div3 触发事件");
},true)
可以看到使用事件捕获的方式触发事件回调和使用事件冒泡的方式触发事件回调,结构是差不多的,只不过是把事件监听的第三个参数设置为了true,这里需要读者对应js的事件监听有一定理解,如果我们不加这个第三个参数,那么也是默认为false,即使用事件冒泡模式来进行事件监听的回调触发,如上面代码我们没有设置第三个参数,浏览器还是默认许可了事件冒泡机制的存在;
那么对上面的代码进行一个执行:
可以看到触发事件的流程完全反过来了,因为事件捕获就是和事件冒泡完全相反的过程;
事件捕获就可以用上面的事件流来理解,我们触发目标元素的监听回调时,事件流会一层一层向容器内部寻找目标元素,在这个过程中会经过其所有的祖先元素,那么事件捕获就做了这样一件事,看这个目标元素的祖先元素上有没有和目标元素同类型的监听,如果有,就顺便触发了,然后继续寻找目标元素;
可以看到上述代码中的元素都设置了鼠标点击事件,所以就从外到内依次触发了;
事件冒泡&事件捕获的好处
这样的好处在于不用为每一个元素都绑定相应的事件,只需要在其父级元素上绑定事件,将回调指定在父级元素的事件中,当子级元素触发事件,那么由其父级进行相应,并进行相应的DOM操作即可;
举一个最经典的栗子👇
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var ul = document.getElementById("ul")
ul.addEventListener("click",function(e){
e.target.style.color = 'red'
})
</script>
可以通过在ul上绑定事件来监听子元素的状态并触发对应回调,比较方便^_^;
这种方法叫事件委托,有兴趣的读者可以继续了解一下,就是基于事件冒泡机制实现的;
如何阻止冒泡/捕获
阻止事件冒泡
在event事件对象中提供了对应的方法;
通过e.stopPropagation()方法阻止事件冒泡:
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
var div1 = document.getElementById("div1")
var div2 = document.getElementById("div2")
var div3 = document.getElementById("div3")
div1.addEventListener("click", function (e) {
console.log("div1 触发事件");
})
div2.addEventListener("click", function (e) {
e.stopPropagation()
console.log("div2 触发事件");
})
div3.addEventListener("click", function (e) {
console.log("div3 触发事件");
})
</script>
上面的代码在div2阻止了事件冒泡,读者可以想一下最后输出结果是什么;
在div2中阻止了事件冒泡,所以事件冒泡不会触发div1中的事件监听回调;
阻止事件捕获
事件对象也提供了对应的方法阻止事件捕获:
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
var div1 = document.getElementById("div1")
var div2 = document.getElementById("div2")
var div3 = document.getElementById("div3")
div1.addEventListener("click", function (e) {
console.log("div1 触发事件");
},true)
div2.addEventListener("click", function (e) {
e.stopPropagation()
console.log("div2 触发事件");
},true)
div3.addEventListener("click", function (e) {
console.log("div3 触发事件");
},true)
可以看到也是用了阻止冒泡的方法,所以stopPropagation方法既可以阻止冒泡,也可以阻止捕获;
那么MDN也给我们提供了另一种解决方案:event.stopImmediatePropagation - Web API 接口参考 | MDN (mozilla.org)
event.stopImmediatePropagation()方法;
具体解释是:如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation()
,那么剩下的事件监听器都不会被调用。
这里笔者不一一赘述,感兴趣的读者可以自行查阅;
以上就是我对事件冒泡和事件捕获的全部理解,若有误请联系我改正