前端学习笔记 - 事件传播

90 阅读3分钟

假设有三个形成父子关系的元素:

Screenshot 2025-01-21 at 19.54.58.png

并且为这个三个元素都添加了onclick事件处理函数。如果用户点击中间的蓝色方块,则会在珊瑚色方块和绿色方块中都触发点击事件。但是哪个事件会先触发呢?换句话说,事件的处理顺序是什么?

两种模型:

在过去,NetscapeMicrosoft得出了不同的结论:

  • Netscape认为事件应该由外向内传播,即祖先元素上的事件先发生。
  • Microsoft则认为事件应该由内向外传播,即目标元素上的事件优先。

这两种模型的事件顺序完全相反。

W3C模型

W3C综合两个模型的方案,将事件传播分为两个阶段:

1. 捕获阶段

用户点击蓝色方块,开始事件的捕获阶段:

  1. 查找蓝色方块的任何祖先元素是否注册了onclick捕获阶段的处理函数。
  2. 从最外层的元素开始(绿色方块),如果找到,则执行相应的处理函数。
  3. 事件继续向内传播,依次执行各个元素上的事件处理函数,直到目标元素(蓝色方块)。

Screenshot 2025-01-21 at 18.49.03.png

2. 冒泡阶段

事件向下传递到目标元素(即蓝色方块),没有更多的捕获阶段回调函数要处理,事件转移至冒泡阶段:

  1. 查找当前元素是否已注册冒泡阶段处理函数,有则执行。
  2. 事件再次向外传播并执行对应元素上冒泡阶段处理函数。
  3. 直到最外层的祖先元素(绿色方块),事件冒泡结束。

Screenshot 2025-01-21 at 19.01.24.png

事件使用

冒泡阶段回调函数

可以通过下面三种方式添加冒泡阶段回调函数:

  • 直接在标签中使用onclick属性绑定:
<body>
  <div id="box1" onclick="(()=>console.log('green box'))()">
    <div id="box2" onclick="(()=>console.log('coral box'))()">
      <div id="box3" onclick="(()=>console.log('blue box'))()"></div>
    </div>
  </div>
</body>
  • 使用onclick方法绑定:
  <script type="text/javascript">
    window.onload = () => {
      const box1 = document.getElementById('box1')
      const box2 = document.getElementById('box2')
      const box3 = document.getElementById('box3')

      box1.onclick = () => {
        console.log('green box')
      }

      box2.onclick = ()=> {
        console.log('coral box')
      }

      box3.onclick = () => {
        console.log('blue box')
      }
    }
  </script>
  • 使用addEventListener方法绑定:
  <script type="text/javascript">
    window.onload = () => {
      const box1 = document.getElementById('box1')
      const box2 = document.getElementById('box2')
      const box3 = document.getElementById('box3')

      box1.addEventListener('click', () => {
        console.log('green box')
      })

      box2.addEventListener('click', () => {
        console.log('coral box')
      })

      box3.addEventListener('click', () => {
        console.log('blue box')
      }, false)
    }
  </script>

⚠️注意:

  • 标签的onclick属性和onclick方法注册的是冒泡阶段回调函数。
  • addEventListener方法在不指定第三个参数或第三个参数为false时,默认注册的是冒泡阶段回调函数。

取消事件冒泡

可以通过下面设置来阻止事件传播:

  • 【已弃用,仍有部分浏览器支持】将事件对象cancelBubble属性设置为true:
  <script type="text/javascript">
    window.onload = () => {
      const box1 = document.getElementById('box1')
      const box2 = document.getElementById('box2')
      const box3 = document.getElementById('box3')

      box1.addEventListener('click', () => {
        console.log('green box')
      })

      box2.addEventListener('click', () => {
        console.log('coral box')
      })

      box3.addEventListener('click', (e) => {
        console.log('blue box')
        e.cancelBubble = true
      })
    }
  </script>
  • 调用事件对象stopPropagation方法:
  <script type="text/javascript">
    window.onload = () => {
      const box1 = document.getElementById('box1')
      const box2 = document.getElementById('box2')
      const box3 = document.getElementById('box3')

      box1.addEventListener('click', () => {
        console.log('green box')
      }, false)

      box2.addEventListener('click', () => {
        console.log('coral box')
      }, false)

      box3.addEventListener('click', (e) => {
        console.log('blue box')
        e.stopPropagation()
      }, false)
    }
  </script**>**

通过上述设置,事件在处理完蓝色方块的回调函数后就会停止传播,祖先元素的回调函数将不会被执行。

捕获阶段回调函数

通过指定addEventListener方法的第三个参数为true,注册捕获阶段回调函数:

  <script type="text/javascript">
    window.onload = () => {
      const box1 = document.getElementById('box1')
      const box2 = document.getElementById('box2')
      const box3 = document.getElementById('box3')

      box1.addEventListener('click', () => {
        console.log('green box')
      }, true)

      box2.addEventListener('click', () => {
        console.log('coral box')
      }, true)

      box3.addEventListener('click', () => {
        console.log('blue box')
      }, true)
    }
  </script>

参考资料