浏览器的事件流机制你知道多少?

111 阅读3分钟

请看以下代码

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box1{
      width: 500px;
      height: 500px;
      background-color: red;
    }
    .box2{
      width: 300px;
      height: 300px;
      background-color: green;
    }
   .box3{
      width: 100px;
      height: 100px;
      background-color: blue;
    }
  </style>
</head>
<body>
  <div class="box1">
    <div class="box2">
      <div class="box3"></div>
    </div>
  </div>

  <script>
    let box1 = document.querySelector('.box1');
    let box2 = document.querySelector('.box2');
    let box3 = document.querySelector('.box3');

    box1.addEventListener('click',function(){
      console.log('box1 red');
    }, true)
    box2.addEventListener('click',function(){
      console.log('box2 green');
    }, false)
    box3.addEventListener('click',function(){
      console.log('box3 blue');
    }, false)
  </script>
</body>
</html>

image.png

大家可以猜测以下,假设我点了蓝色方块,代码会怎么触发呢?

想要知道答案,我们需要先了解以下浏览器的事件流。

什么是事件流呢,在浏览器中,事件流(Event Flow) 是指 事件从触发到处理的传播过程。它描述了事件如何从文档树的根节点传递到目标元素,然后再从目标元素返回到根节点的过程。


一、事件流的三个阶段

事件流分为三个阶段,依次为:

  1. 捕获阶段(Capture Phase)

    • 事件从 window 开始,沿着 DOM 树向下传播,直到到达目标元素。
    • 这个阶段通常用于提前拦截事件
  2. 目标阶段(Target Phase)

    • 事件到达目标元素,触发目标元素上的事件处理程序。
  3. 冒泡阶段(Bubble Phase)

    • 事件从目标元素开始,沿着 DOM 树向上传播,直到回到 window
    • 这个阶段是最常用的事件处理阶段

二、事件流的传播过程

以下是一个典型的事件流传播过程:

  1. 捕获阶段windowdocument<html><body> → 目标元素的父元素。
  2. 目标阶段:目标元素。
  3. 冒泡阶段:目标元素的父元素 → <body><html>documentwindow

三、事件流的代码示例

简单了解之后,我们来分析一下以下代码吧。

<div id="outer">
  <div id="inner">点击我</div>
</div>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");

// 捕获阶段
outer.addEventListener("click", () => {
  console.log("捕获阶段:outer");
}, true); // 第三个参数为 true,表示在捕获阶段触发

// 目标阶段
inner.addEventListener("click", () => {
  console.log("目标阶段:inner");
});

// 冒泡阶段
outer.addEventListener("click", () => {
  console.log("冒泡阶段:outer");
}, false); // 第三个参数为 false(默认),表示在冒泡阶段触发

点击 inner 元素后的输出

捕获阶段:outer
目标阶段:inner
冒泡阶段:outer

所有我们可以知道,当我们点击了‘点击我’,首先会捕获到outer,然后是inner,到达目标之后呢,在从inner返回到outer。

当我们不设置addEventListener的第三个参数,则默认false,在冒泡阶段触发,也就是先触发inner的点击事件,然后是outer的点击事件。

目标阶段:inner
冒泡阶段:outer

反之第三个参数设置为true,在捕获阶段触发。就会先触发outer的点击事件,然后inner是的点击事件。

捕获阶段:outer
目标阶段:inner

四、事件流的控制方法

  1. 阻止事件传播

    • 使用 event.stopPropagation() 阻止事件继续传播(捕获或冒泡)。
    • 使用 event.stopImmediatePropagation() 阻止事件传播,并阻止同一元素上的其他事件处理程序执行。
    inner.addEventListener("click", (event) => {
      console.log("inner 点击");
      event.stopPropagation(); // 阻止事件冒泡
    });
    
  2. 阻止默认行为

    • 使用 event.preventDefault() 阻止事件的默认行为(如表单提交、链接跳转)。
    const link = document.querySelector("a");
    link.addEventListener("click", (event) => {
      event.preventDefault(); // 阻止链接跳转
      console.log("链接点击被阻止");
    });
    

五、事件委托(Event Delegation)

事件委托是一种利用事件冒泡机制的优化技术。将事件监听器绑定到父元素,通过事件冒泡处理子元素的事件。

1. 优点

  • 减少事件监听器的数量,提升性能。
  • 动态添加的子元素无需重新绑定事件。

2. 代码示例

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
const list = document.getElementById("list");

list.addEventListener("click", (event) => {
  if (event.target.tagName === "LI") {
    console.log("点击了:", event.target.textContent);
  }
});

六、总结

  • 事件流分为捕获、目标、冒泡三个阶段。
  • 捕获阶段:从 window 到目标元素。
  • 目标阶段:事件到达目标元素。
  • 冒泡阶段:从目标元素回到 window

这下我们就能理解开头的代码返回的是什么了,大家可以在评论区讨论讨论!