超详细!!关于事件冒泡/事件捕获,事件代理(事件委托)这里有你所需要知道的一切。

3,698 阅读8分钟

本人已参与「新人创作礼」活动,一起开启掘金创作之路。

目录

  • 什么是事件冒泡
  • 什么是事件捕获
  • 如何控制事件在冒泡阶段执行还是捕获阶段执行
  • 什么是事件代理
  • 事件代理和事件委托的区别
  • 事件代理在实际应用中到底有什么用?
  • 如何阻止事件冒泡

关于事件代理的一切

什么是事件冒泡

事件冒泡就是:当某元素执行某一种类型事件,那么从该元素起逐级向外层的元素检测是否存在与本身同样的事件。这一个过程,就叫做事件冒泡。

举个例子:

image.png 有这么一个结构

div1嵌套着div2

div2嵌套着div3

然后我们给这三个元素分别绑定一个click事件并输出他们自身的class。

(div1的class为"div1",div2的class为"div2",div3的class为"div3") image.png

问题是:在事件冒泡的阶段点击div3的话,输出的结果是什么?

答案是:div3,div2,div1都会被输出。

image.png

当div3的事件被触发后,因为事件冒泡的机制,浏览器会去div3的父元素(也就是div2)身上寻找同类型事件,如果找到了。则触发。注意:不论有没有在div2上找到同类型事件,冒泡会一直传递下去,一直到Document为止。

这就是事件冒泡

所以,在事件冒泡的前提下,三个嵌套div的输出顺序是:div1 > div2 > div3

什么是事件捕获。

当我们了解了什么是事件冒泡后,事件捕获就变得简单起来了。

因为,事件捕获刚好就和事件冒泡反过来。

事件捕获就是:当元素被触发事件时候,从该元素的根节点开始逐级向里寻找同类型事件。这个过程,就被称为事件捕获

还是拿div1,div2,div3举个例子。

image.png

在事件捕获的状态下。点击div3并不会优先触发div3的事件。而会先触发div1的事件,再然后是div2,最后才是div3。因为事件捕获是从根节点开始向内查找的。外部的元素上的事件会优先被检测到并执行。

所以在事件捕获的状态下,三个div的输出顺序是:div1 > div2 > div3

总结下事件冒泡和事件捕获的区别,事件冒泡是从元素本身开始,逐级向上寻找同类型事件并执行,一直到document为止。事件捕获是从document开始,逐级向下寻找同类型事件,并执行,一直到触发该事件的元素为止。

如何控制事件在冒泡阶段执行还是捕获阶段执行

image.png

我们先来看看官方提供的DOM事件流向模型图。

图上说明了:↓

事件的流向有三个阶段:捕获阶段,目标阶段,冒泡阶段。

红色的部分代表事件捕获,蓝色部分代表目标阶段。绿色的线代表冒泡阶段。

由图可知,当一个元素身上的事件被触发后,会优先从根节点进行事件捕获(捕获阶段),然后寻找到自身为止(目标阶段),最后从自身往外层逐级冒泡(冒泡阶段)。

所以,一次事件当执行,是走以下流程的

先进行事件捕获 => 再到目标本身 => 最后再进行事件冒泡

有的小伙伴可能就会疑惑了:如果事件每次触发都需要把冒泡和捕获都走一遍,那岂不是每个事件都需要触发两次?

不会的。

这里需要注意:事件冒泡和事件捕获本身并不会主动触发事件。需要我们决定事件在冒泡阶段执行还是在捕获阶段执行。

我们介绍一下: addEventListener函数。

addEventListener函数用于事件绑定,他有三个参数↓

  1. eventType 事件类型("click之类的")
  2. function 触发事件后所需要执行的函数
  3. bool 入参true/false,决定事件在冒泡阶段执行还是捕获阶段执行。 true表示事件在捕获阶段执行,false表示事件在冒泡阶段执行。默认值是false。

还是拿这三个div举例子

image.png (当第三个入参为false时)

image.png (当第三个入参为true时)

由图可知:

当addEventListener函数的第三个参数输入的是false的时候。点击div3,事件会从div3开始执行,并逐级向上执行。(冒泡阶段执行)

当addEventListener函数的第三个参数输入的是true的时候。点击div3,事件会从div1开始执行,并逐级向下执行。(捕获阶段执行)

小知识↓

事件冒泡和事件捕获好像存在一个就足够了,为什么会有这两套概念?答:因为事件冒泡是微软公司提出来了。而事件捕获是网景公司提出来了,删掉那个都不好,干脆就两个都留下来了。

需要注意↓

在我们没有做任何处理的情况下,我们所绑定的事件,默认都在冒泡阶段执行。

什么是事件代理

事件代理:事件代理就是利用事件冒泡或事件捕获的机制把一系列的内层元素事件绑定到外层元素。(这也就意味着子元素本身将不用注册自己的事件)

举个例子↓

image.png

看上图,不难看出,其实只有div1(最外层的元素)被绑定了事件。但为什么可以执行输出div2和div3呢?

他的执行流程是↓

1.当我们点击div2(或div3)的时候,浏览器发现div2/div3本身没有事件

2.通过冒泡的方式检查到div3,发现div3中有一个click事件,就执行div3的click事件

3.这里注意:e表示我们当前点击的事件,所以即使事件在div3执行。e指向的还是div2(或div3),所以可以输出div2(或div3)的内容。

辟谣

网上有些小伙伴会说,只有事件冒泡阶段才可以有事件代理。

并不是!!!

实际上,不论是事件冒泡阶段还是事件捕获阶段,都可以完成事件代理。

冒泡阶段的事件代理写法↓

   const parDom = document.getElementById("某ID");
    parDom.addEventListener(
    'click', 
    (e) => {console.log('冒泡阶段的事件代理')}, 
    false);

捕获阶段的事件代理写法↓

   const parDom = document.getElementById("某ID");
    parDom.addEventListener(
    'click', 
    (e) => {console.log('捕获阶段的事件代理')}, 
    true);

事件代理和事件委托的区别

没有区别

image.png

叫法不同而已。事件代理和事件委托是同一个东西。

所以小伙伴注意:如果以后面试官问你事件代理和事件委托有什么区别,八成是在给你下套。

强调一遍:事件代理和事件委托这两个没有区别!!仅仅只是叫法不同而已。

事件代理在实际应用中到底有什么用?

事件代理(事件委托)的适用场景其实我们大家接触到的还是蛮多的,只是大家可能都没发现还能这么玩而已。

事件代理的作用就是:避免大量事件注册

举个例子↓

咱们有一个列表,列表项需要绑定事件。

image.png

像这样,是不是就需要注册9个事件?上图只有9个,如果这个列表有1万行。难道我们就需要去注册1万个事件吗?这显然不合理。所以事件代理诞生了。把事件注册到父元素上,不论我们有多少个列表,都只需要在父元素上注册一个事件就好了。

我们对上图的列表注册做一个优化↓

image.png

这样,我们就将N个事件合并为一个事件了!

对性能更友好。

image.png

如何阻止事件冒泡/捕获

阻止事件冒泡/捕获的方法 : event.stopPropagation( )

例如:

  function fun(event){
      ...
      event.stopPropagation( )
  }

尽量不要用event.stopPropagation()去阻止捕获阶段执行的事件。因为捕获阶段是从外到内,如果我们要触发内部的某一个事件,那么event.stopPropagation()会让浏览器检测在第一层就阻止往下捕获,也就检测不到我们写在内部的事件。所以event.stopPropagation()在事件冒泡阶段执行的函数上用用就好了。 image.png

总结

什么是事件冒泡↓

事件冒泡就是:当某元素执行某一种类型事件,那么从该元素起逐级向上面的元素检测是否存在与本身同样的事件。这一个过程,就叫做事件冒泡。

什么是事件捕获↓

当元素被触发事件时候,从该元素的根节点开始逐级向下寻找同类型事件。这个过程,就被称为事件捕获

如何控制事件在冒泡阶段执行还是捕获阶段执行

addEventListener(“event”,fun,bool)的第三个入参,false代表事件在冒泡阶段执行,true代表事件在捕获阶段执行。默认都是fasle。

什么是事件代理

事件代理就是利用事件冒泡或事件捕获的机制把一系列的内层元素事件绑定到外层元素。交给外层元素统一触发。

事件代理和事件委托的区别

没区别,叫法不同而已。

事件代理(事件委托)在实际应用中到底有什么用?

避免大量事件注册

如何阻止事件冒泡/捕获

在函数体中写 event.stopPropagation()即可阻止冒泡或捕获

附加↓

在我们没有做任何处理的情况下,我们所绑定的事件,默认都在冒泡阶段执行。