前端开发:JS的事件冒泡和事件捕获详解

270 阅读7分钟

我正在参加「掘金·启航计划

前言

在前端开发过程中,关于JS原生的核心内容使用是日常工作中的常态,关于底层和原理的掌握使用,尤其是在性能优化方面甚为重要。作为前端开发的进阶内容,在实际开发过程中事件发生的顺序称为事件流,当触发某个事件的时候会引起一系列的连锁反应,但是当业务场景中页面功能嵌套了某几个元素来处理同一个事件,那么哪个事件会先被触发?这就需要了解事件传播当顺序。也就是当有多个盒子嵌套且多层盒子都存在事件的情况下,需要了解这些事件的发生顺序,JS的事件冒泡和事件捕获就是用来处理事件流中的事件发生顺序的,所以在JS中事件冒泡和事件捕获的使用也是比较常用的知识点,而且在前端求职面试的时候二者也是必考知识点,可以说非常重要,那么本文就来做一下总结,方便查阅使用。

事件流

在介绍事件冒泡和事件捕获之前,首先来了解一下事件流的概念。事件流指的是事件完整执行过程中的流动路径 ,也就是说事件流是网页中元素接受事件的顺序,事件流即事件发生的顺序。事件流分为事件冒泡阶段和事件捕获阶段 ,其中,事件冒泡阶段是从子到父;事件捕获阶段是从父到子。

其实,DOM(文档对象模型)结构是一个树型的结构,当一个HTML元素产生一个事件的时候,该事件会在元素节点与根节点之间的路径上传播,路径所经过的节点都会收到该事件,而这个传播过程的顺序就是DOM事件流。

注意:众所周知,目前前端领域使用的DOM标准事件流传播顺序是按照W3C统一之后的标准,即:先捕获后冒泡,也就是说当触发DOM事件的时候,会先进行事件捕获,捕获到事件源头之后再通过事件传播进行事件冒泡。

addEventListener()方法

在来看一下使用事件冒泡和捕获的时候必用的addEventListener()方法,它其实是一个给事件绑定监听函数的方法,可以接收三个参数,具体如下所示:

element.addEventListener(event, function, useCapture)

其中,第一个参数:event,指的是需要绑定的事件(字符串,指定事件名,不要用on前缀,用click,而不是用onclick);

第二个参数:function,指的是指定事件触发时执行的函数;

第三个参数: useCapture,指的是指定事件流为事件冒泡,默认为false;如果设为true,指定事件流为事件捕获。

注意:addEventListener()方法和原生的“on+事件类型”方法(简称"on()"方法)最大的区别是on()方法会被后面的on()方法覆盖掉,但是addEventListener()方法不会。

事件冒泡

image.png

在JS中,事件冒泡其实就是事件从目标事件起始,沿着父节点依次向上,且触发父节点上的事件,直至文档根节点。事件发生的顺序就像一个气泡从水底上浮一样:从底到顶、从内到外的顺序,会一直向上,直到水面为止。事件冒泡允许多个操作被集中处理,可以把事件处理器添加到父级元素上,这样可以避免事件处理器添加到多个子级元素上,事件可以在对象层的不同层级中捕获事件。

引申:事件冒泡是由网景公司提出的概念,事件冒泡是从事件源头-->根节点进行事件传播,也就是从内到外的顺序进行事件传播的。

事件冒泡示例

本文分享一个简单的关于事件冒泡的示例,方便理解它的原理,具体代码如下所示:

export default {

data(){

return {}

},

methods: {

let first = document.getElementById('first');

let second = document.getElementById('second');

let third = document.getElementById('third');

first.addEventListener('click',function(){

console.log("这是first的点击事件");

},false);

second.addEventListener('click',function(){

console.log("这是second的点击事件");

// event.stopPropagation(); //这就是阻止当前节点事件继续冒泡的方法

},false);

third.addEventListener('click',function(){

console.log("这是third的点击事件");

},false);

}

};

输出结果为

这是third的点击事件

这是second的点击事件

这是first的点击事件

事件捕获

image.png

在JS中,事件捕获其实就是事件从DOM树最外层开始,依次经过目标节点的各个父级节点,并在这些节点上触发父节点上面的事件,直至到达事件的目标节点为止。

引申:事件捕获是由微软公司提出的概念,事件从根节点(Document 对象)-->事件的目标节点的流向顺序,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达事件的目标节点。也就是从外到内的顺序进行事件传播的。

事件捕获示例

本文分享一个简单的关于事件捕获的示例,方便理解它的原理,具体代码如下所示:

export default {

data(){

return {}

},

methods: {

let first = document.getElementById('first');

let second = document.getElementById('second');

let third = document.getElementById('third');

first.addEventListener('click',function(){

console.log("这是first的点击事件");

},true);

second.addEventListener('click',function(){

console.log("这是second的点击事件");

// event.stopPropagation(); //这就是阻止当前节点事件继续捕获的方法

},true);

third.addEventListener('click',function(){

console.log("这是third的点击事件");

},true);

}

};

输出结果为:

这是first的点击事件

这是second的点击事件

这是third的点击事件

阻止事件捕获和事件冒泡

场景:上面只是介绍了通用的用法,一条线执行,但是实际业务场景并没有这么单一,比如有些时候在某一个节点上绑定了一个事件,但是需要点击时候触发这个事件,可是由于事件冒泡该节点的事件会被其它子元素触发,针对这种情况就需要阻止掉。

针对上面的情况,需要阻止事件冒泡或者事件捕获,在JS中主要是通过使用stopPropagation()方法来阻止,通过使用它可以阻止事件沿着DOM树向上或者向下进行进一步的传播。具体示例代码如下所示:

event.stopPropagation();

也就是直接在相应的处理函数内,加入event.stopPropagation(),终止向上/向下进一步传播,这样事件停留在本节点,不会再往外传播了,完美解决上面的需求点。

事件冒泡和事件捕获同时存在

当同时存在冒泡事件和捕获事件的时候,捕获事件的优先级比冒泡事件的优先级高,且上文已经介绍过,捕获事件是从外到内的,而冒泡事件是从内到外的。具体的代码示例这里就不再演示。

拓展:事件委托

事件委托是利用事件冒泡的原理。 使用情况:当有多个类似的元素需要绑定事件时,一个一个去绑定既浪费时间,又不利于性能,这时候可以使用事件委托,给他们的一个共同父级元素添加一个事件函数去处理所有的事件情况。

export default {

data(){

return {}

},

methods: {

father.addEventListener("click",function(e){

if(e.target!=father){

console.log(`这是${e.target.innerText}的点击事件`)

}

});

}

};

输出结果为

点击哪个li,就输出"这是哪个li的点击事件"

从上面的示例可以看出,由于采用的是事件冒泡的事件流,所以当点击li元素的时候,向上触发它们的父元素上的点击事件。

这样做的好处就是:提高了性能,不需要循环所有的元素,然后一个个绑定对应的事件;使用灵活,当有新的子元素时不需要再重新绑定事件。

取消冒泡事件:以W3C标准的话,直接使用event.stopPropagation()方法即可,但是IE9以下版本不支持该方法,需要使用scroll、mouseleave、mouseenterfocus、blur、focus、change、submit、reset、select等事件阻止冒泡,但是hover事件不能使用事件委托的方式。

最后

通过本文关于前端开发JS中的事件冒泡和事件捕获的详细介绍,不管是在实际的前端开发工作中还是在前端求职面试中都是非常关键的知识点,所以作为前端开发者来说必须要掌握它相关的内容,尤其是从事前端开发不久的开发者来说尤为重要,是一篇值得阅读的文章,重要性就不在赘述。欢迎关注,一起交流,共同进步。