面试题 -- 怎么阻止事件捕获?

6,138 阅读3分钟

1、前言

本文中的所有代码demo均在chrome浏览器下运行。

阅读本文,你将学到:

1. 浏览器默事件传播机制
2. 捕获、冒泡的机制
3. 阻止捕获、冒泡
4. `stopImmediatePropagation``stopPropagation`区别

2、事件传播机制

2.1 浏览器事件默认传播机制

嵌套元素,默认的事件传播方向是什么(默认情况下,addEventListener第三个参数为false), 先写两个元素div1div2,两个元素是父子关系:

<style>
  #div1 {
    width: 300px;
    padding: 15px;
    border: 2px solid black;
  }
  #div2 {
    width: 100px;
    padding: 5px;
    border: 2px solid darkorange;
  }
</style>

<body>
    <div id="div1">
      div1
      <div id="div2">
          div2
      </div>
    </div>
</body>

js部分:

const div1 = document.querySelector("#div1");
const div2 = document.querySelector("#div2");

div1.addEventListener("click", function () {
    console.log("div1被点击");
  });

div2.addEventListener("click", function () {
    console.log("div2被点击");
});

点击div2, 依次打印:div2被点击, div1被点击,

结论:默认是从目标元素开始,冒泡执行(从里向外传播)

2.2 手动设置在捕获或冒泡阶段执行

设置(addEventListener第三个参数为true)div1在捕获阶段执行, div2不设置第三个参数(其实不管设置了true还是false,结果是一样的)

div1.addEventListener(
    "click",
    function () {
      console.log("div1被点击");
    },
    true // 新增++
  );

div2.addEventListener("click", function () {
    console.log("div2被点击");
});

点击div2, 依次打印:div1被点击, div2被点击

结论:如果外层元素设置在捕获阶段执行,那么会先触发外层元素的绑定事件

3、阻止冒泡、捕获

3.1 点击div2的时候,怎么阻止事件向外层传递(阻止冒泡)?

div1.addEventListener("click", function () {
    console.log("div1被点击");
  });

  div2.addEventListener("click", function (e) {
    console.log("div2被点击");
    e.stopPropagation();
    // e.stopImmediatePropagation(); // 用这个也能阻止
  });

只打印:div2被点击

结论:div2中使用stopPropagationstopImmediatePropagation方法阻止(下边会解释这两个api的区别)

3.2 点击div2, 如果div1、div2都在捕获阶段执行,怎么阻止捕获?

div1.addEventListener(
    "click",
    function (e) {
      console.log("div1被点击");
      e.stopPropagation();
      // e.stopImmediatePropagation(); // 也能阻止捕获
    },
    true
  );

  div2.addEventListener(
    "click",
    function (e) {
      console.log("div2被点击");
    },
    true
  );

只打印:div1被点击

结论:可以使用stopPropagationstopImmediatePropagation方法阻止事件在捕获阶段传播

4、stopImmediatePropagation

既然stopPropagationstopImmediatePropagation都可以阻止冒泡或者阻止捕获,那有什么区别呢?

我们再给div2加一个click事件,然后在div2第一个click事件处理函数中添加stopPropagation方法,会发生什么呢?

我们先看stopPropagation

div1.addEventListener("click", function (e) {
    console.log("div1被点击");
  });

  div2.addEventListener("click", function (e) {
    e.stopPropagation();
    console.log("事件1  div2被点击");
  });

  div2.addEventListener("click", function (e) {
    console.log("事件2  div2被点击");
  });

不出意外,会依次打印:事件1 div2被点击, 事件2 div2被点击(没有打印div1被点击是因为,在div2中阻止冒泡了);

div2的第二个click事件代码放到div2第一个click事件代码前边,会依次打印事件2 div2被点击, 事件1 div2被点击;

结论:当同一个元素绑定多个相同事件的时候,执行顺序和代码的前后顺序有关,也就是和绑定的顺序有关

接下来看stopImmediatePropagation

div1.addEventListener("click", function (e) {
    console.log("div1被点击");
  });

  div2.addEventListener("click", function (e) {
    e.stopImmediatePropagation();
    console.log("事件1  div2被点击");
  });

  div2.addEventListener("click", function (e) {
    console.log("事件2  div2被点击");
  });

只打印:事件1 div2被点击

结论:stopImmediatePropagation会阻断相同元素的后续的相同事件的执行。

5、总结

  • Chrome浏览器的默认事件传播顺序是:目标元素 --> 向上冒泡;

  • stopImmediatePropagationstopPropagation区别:

    a.stopPropagation: 可以阻止事件在冒泡或捕获阶段传递,但是不能阻止监听同一事件的其他事件监听器被调用;

    b.stopImmediatePropagation: 如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用。