目录
前言
在JavaScript中,我们将事件发生的顺序成为“事件流”,当我们触发某个事件时,会发生一系列的连锁反应。
但是,如果我们有几个嵌套的元素来处理同一个事件(如下图),到底哪个事件会先被触发呢?所以,我们需要理解事件传播顺序。
微软和网景这两个公司提出了两种不同的概念,分别是事件捕获和事件冒泡。
事件捕获是由微软公司提出的,当鼠标点击或触发DOM事件时(被触发DOM事件的这个元素被叫做事件源),浏览器会从根节点=>事件源(由外层到内层)进行事件传播。
事件冒泡是由网景公司提出的,它的传播顺序与事件捕获相反,是从事件源=>根节点(由内层到外层)进行事件传播。
我们现在使用DOM标准事件流的传播顺序的是W3C统一后的标准——先捕获后冒泡。即当出发DOM事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。如下图。
我们还需要了解DOM事件流(event flow)存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
简单了解过后,下面我们来看事件捕获和事件冒泡是如何出现的。
事件捕获(event capturing)
事件捕获是由外到内的。在事件捕获阶段,事件会从 DOM 树的最外层开始,依次经过目标节点的各个父节点,并触发父节点上的事件,直至到达事件的目标节点。如下图。
上图的例子中,事件处理顺序是Window=>Document=>box1=>box2=>a。除此之外,我们还可以观察到:事件捕获只发生在被点击的元素或目标上,该事件不会传播到子元素。
我们用到了addEventListener方法,在一般情况下,该方法只会用到两个参数,一个是需要绑定的事件,另一个是触发事件后要执行的函数,但是,addEventListener还可以传入第三个参数,该参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果该参数为true,则表示在事件捕获阶段调用处理函数。
JavaScript测试部分代码如下:
<script>
window.addEventListener("click",()=>{
console.log("Window");
},true);
document.addEventListener("click",()=>{
console.log("Document");
},true);
document.querySelector("#box2").addEventListener("click",()=>{
console.log("box2");
},true);
document.querySelector("#box1").addEventListener("click",()=>{
console.log("box1");
},true);
document.querySelector("a").addEventListener("click",()=>{
console.log("Click me");
},true);
</script>
事件冒泡(dubbed bubbling)
上面我们说了事件捕获,事件冒泡和事件捕获是完全相反的。事件冒泡是由内到外的,它是从目标节点开始,沿父节点依次向上,并触发父节点上的事件,直至文档根节点,就像水底的气泡一样,会一直向上。如下图。
上图的例子中,事件处理顺序是Window<=Document<=box1<=box2<=a。
上面我们说到,addEventListener的第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数,这说明事件监听器默认监听冒泡事件。
JavaScript测试部分代码如下:
<script>
window.addEventListener("click",()=>{
console.log("Window");
},false);
document.addEventListener("click",()=>{
console.log("Document");
},false);
document.querySelector("#box2").addEventListener("click",()=>{
console.log("box2");
},false);
document.querySelector("#box1").addEventListener("click",()=>{
console.log("box1");
},false);
document.querySelector("a").addEventListener("click",()=>{
console.log("Click me");
},false);
</script>
阻止事件捕获和事件冒泡
在我们了解事件捕获和事件冒泡后,难免会思考一个问题:如果我们在某个节点上绑定了一个事件,我们需要在点击时触发这个事件,但是由于事件冒泡,这个节点的事件被它的子元素触发了,我们应该怎么阻止这种情况发生呢?
我们可以使用stopPropagation()方法来阻止这种情况发生,它将阻止事件沿着DOM树向上或向下进一步传播。如下图。
JavaScript测试部分代码如下:
<script>
window.addEventListener("click",(e)=>{
console.log("Window");
e.stopPropagation();
},false);
document.addEventListener("click",(e)=>{
console.log("Document");
e.stopPropagation();
},false);
document.querySelector("#box2").addEventListener("click",(e)=>{
console.log("box2");
e.stopPropagation();
},false);
document.querySelector("#box1").addEventListener("click",(e)=>{
console.log("box1");
e.stopPropagation();
},false);
document.querySelector("a").addEventListener("click",(e)=>{
console.log("Click me");
e.stopPropagation();
},false);
</script>