事件传播\捕获\冒泡\委托分别是什么?

1,074 阅读3分钟

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

Hi~,我是一碗周,一个在舒适区垂死挣扎的前端,如果写的文章有幸可以得到你的青睐,万分有幸~~

🍓 写在前面

说道DOM事件,肯定离不开事件传播\捕获\冒泡\委托这些花里胡哨的东西,这些东西还是事件中比较重要的内容,这篇文章会来介绍一下这些花里胡哨的东西。

🥭 事件传播

事件传播也叫事件流,就是当触发某个元素的事件时,事件会按照DOM结构树进行传播,传播的过程如下:

  1. 捕获阶段:按照DOM结构由window对象向下的顺序传播,直到目标元素为止,这也就是事件捕获;
  2. 目标阶段:该阶段就是值目标元素触发当前事件;
  3. 冒泡阶段:按照DOM结构树由目标元素向上的顺序传播,直到window对象,这也就是事件冒泡。

这三个阶段的过程如下所示:

事件传播.png

上图就是事件传播的一个过程。

🍇 事件捕获与冒泡

上面那张图中DOM结构如果用HTML表示如下:

<html>
  <body>
    <div id="div">
      <p id="p">
        <a id='a'></a>
      </p>
    </div>
  </body>
</html>

当我们点击<a>这个元素的时候,事件会从Window对象开始逐层传递,直到<a>触发click事件,然后执行我们的回调函数;然后执行完毕之后会逐层向上冒泡,然后就会执行为<div>以及父元素绑定的事件处理函数。

事件处理函数如下:

window.addEventListener('click',()=>{
  console.log('window的click触发了')
})
document.addEventListener('click',()=>{
  console.log('document的click触发了')
})
document.documentElement.addEventListener('click',()=>{
  console.log('html的click触发了')
})
document.body.addEventListener('click',()=>{
  console.log('body的click触发了')
})
document.getElementById('div').addEventListener('click',()=>{
  console.log('div的click触发了')
})
document.getElementById('p').addEventListener('click',()=>{
  console.log('p的click触发了')
})
document.getElementById('a').addEventListener('click',()=>{
  console.log('a的click触发了')
})

此时点击<a>元素事件函数执行结果如下:

a的click触发了
p的click触发了
div的click触发了
body的click触发了
html的click触发了
document的click触发了
window的click触发了

可见,事件的函数的执行顺序是根据DOM结构依次向上的触发的,直到window为止。

✈️ addEventListener的第三个参数

addEventListener的第三个参数可以控制事件函数的执行是在捕获阶段还是冒泡阶段,默认为false也就是事件在冒泡阶段执行,如果没true则会在捕获阶段执行事件处理函数,如下所示:

window.addEventListener('click',()=>{
  console.log('window的click触发了')
}, true)
document.addEventListener('click',()=>{
  console.log('document的click触发了')
}, true)
document.documentElement.addEventListener('click',()=>{
  console.log('html的click触发了')
}, true)
document.body.addEventListener('click',()=>{
  console.log('body的click触发了')
}, true)
document.getElementById('div').addEventListener('click',()=>{
  console.log('div的click触发了')
}, true)
document.getElementById('p').addEventListener('click',()=>{
  console.log('p的click触发了')
}, true)
document.getElementById('a').addEventListener('click',()=>{
  console.log('a的click触发了')
}, true)

执行结果正好与前面的结果是相反的。

🐬 取消事件冒泡

取消事件冒泡可以通过event对象提供的stopPropagation()方法,示例如下:

document.getElementById('a').addEventListener('click',(e)=>{
  e.stopPropagation()
  console.log('a的click触发了')
})

此时点击<a>仅仅只有一个输出。

🦋 事件委托

事件委托

当为大量的HTML元素注册相同事件,并且事件的句柄逻辑完全相同,会造成页面速度下降。不过,事件流允许这些HTML元素的共同父级注册事件。这种方式称为事件委托。

我们先看一段代码

<body>
  <div id="container">
    <button id="btn1">按钮</button>
    <button id="btn2">按钮</button>
    <button id="btn3">按钮</button>
  </div>
  <script>
    var btn1 = document.getElementById('btn1')
    btn1.addEventListener('click', function () {
      console.log('我是按钮')
    })
    var btn2 = document.getElementById('btn2')
    btn2.addEventListener('click', function () {
      console.log('我是按钮')
    })
    var btn3 = document.getElementById('btn3')
    btn3.addEventListener('click', function () {
      console.log('我是按钮')
    })
  </script>
</body>

现在我们通过事件委托来改写这个代码,代码如下

var container = document.getElementById('container')
container.addEventListener('click', function (event) {
  var target = event.target
  // 当前点击的为 button 时,才会触发下面逻辑代码
  if (target.nodeName == "BUTTON") {
    console.log('我是按钮')
  }
})

我们可以明显看到,代码量的节省,但是最终的运行结果是一样的。

如果在10000万个单元格中为每个单元格绑定事件那就是需要注册10000个事件?很显然不合适,这时候事件委托的作用就体现出来了。

🥪 写在最后

这里简单的回顾了事件传播\捕获\冒泡\委托,也是比较基础的一些内容,不过还是很容易被忽略的,这里做了下总结,方便记忆。