阻止事件冒泡与默认行为:`stopPropagation` 与 `preventDefault`

各位观众,各位听众,前端界的英雄好汉们!晚上好!我是今晚的讲师,一个在代码海洋里摸爬滚打多年的老水手,大家都叫我“Bug终结者”,今天,咱们不聊高并发,不谈大数据,咱们来点轻松有趣的,聊聊前端世界里两位“反骨仔”—— stopPropagationpreventDefault

先别害怕,我说的“反骨仔”可不是贬义词,它们俩就像武侠小说里的侠客,专门阻止一些“恶霸行为”,维护咱们用户的利益,让网页交互更加流畅、更加可控。

今天,咱们就来一场“阻止事件冒泡与默认行为”的深度解剖,用最通俗易懂的语言,最生动形象的比喻,彻底搞懂这两位“反骨仔”的脾气秉性,让它们成为你手中的利器,而不是绊脚石。

一、事件机制:DOM世界的“传话筒”

在深入了解 stopPropagationpreventDefault 之前,我们需要先了解一下DOM(Document Object Model)的事件机制。你可以把它想象成一个信息传递系统,就像古代的烽火台,或者现代的微信群。

当用户在网页上进行操作,比如点击一个按钮、滚动鼠标滚轮、输入文字等等,这些操作都会触发相应的事件。这些事件就像一个个小信使,在DOM树上“奔走相告”,传递信息。

DOM事件机制主要分为三个阶段:

  1. 捕获阶段(Capturing Phase): 事件从根节点(window)开始,沿着DOM树向下传播,直到到达目标元素。就像皇帝发布圣旨,从皇宫一级一级传达到地方官员。
  2. 目标阶段(Target Phase): 事件到达目标元素,也就是用户直接操作的那个元素。就像圣旨最终送到了要执行任务的官员手中。
  3. 冒泡阶段(Bubbling Phase): 事件从目标元素开始,沿着DOM树向上冒泡,直到到达根节点。就像地方官员完成任务后,要一级一级向上汇报情况,直到皇帝知晓。

好了,现在让我们用一个简单的例子来模拟一下这个过程:

<div id="grandpa">
  <div id="father">
    <button id="son">Click Me!</button>
  </div>
</div>

假设用户点击了 <button id="son"> 按钮,那么事件传播的顺序大概是这样:

  1. 捕获阶段: window -> document -> <html> -> <body> -> <div id="grandpa"> -> <div id="father"> -> <button id="son">
  2. 目标阶段: <button id="son">
  3. 冒泡阶段: <button id="son"> -> <div id="father"> -> <div id="grandpa"> -> <body> -> <html> -> document -> window

二、stopPropagation:江湖人称“截胡王”

现在,主角之一 stopPropagation 要登场了!它的作用非常简单粗暴,就是阻止事件继续传播,就像一个“截胡王”,把正在传递的信息直接拦截下来,不让它继续传递下去。

更形象一点说,就像你在微信群里发了一条消息,结果被群主直接撤回了,其他人就看不到了。

stopPropagation 主要用于阻止事件冒泡,也就是说,它可以阻止事件从目标元素向上冒泡到父元素。

我们来修改一下上面的例子,给 father 元素添加一个点击事件监听器,并在监听器中使用 stopPropagation

<div id="grandpa">
  <div id="father">
    <button id="son">Click Me!</button>
  </div>
</div>

<script>
  const father = document.getElementById('father');
  const son = document.getElementById('son');
  const grandpa = document.getElementById('grandpa');

  father.addEventListener('click', function(event) {
    console.log('Father clicked!');
    event.stopPropagation(); // 阻止事件冒泡
  });

  son.addEventListener('click', function(event) {
    console.log('Son clicked!');
  });

  grandpa.addEventListener('click', function(event){
      console.log('Grandpa clicked!');
  });
</script>

现在,如果你点击 <button id="son"> 按钮,控制台会输出:

Son clicked!
Father clicked!

但是 Grandpa clicked! 不会被输出,因为当事件冒泡到 father 元素时,stopPropagation 阻止了事件继续向上冒泡。

为什么要阻止事件冒泡?

你可能会问,为什么要阻止事件冒泡呢?它有什么用呢?

答案是:为了防止不必要的事件触发,提高代码的效率和可维护性。

举个例子,假设你有一个复杂的表单,其中包含多个嵌套的元素,每个元素都有自己的点击事件监听器。如果你不阻止事件冒泡,那么当你点击最内层的元素时,可能会触发所有父元素的点击事件监听器,导致一些意想不到的错误。

另一个常见的应用场景是:自定义组件。当你创建一个自定义组件时,你可能只想让组件内部的事件处理逻辑生效,而不希望影响到组件外部的元素。

stopPropagation 的进阶用法:stopImmediatePropagation

除了 stopPropagation 之外,还有一个更加霸道的“截胡王”—— stopImmediatePropagation

stopImmediatePropagation 不仅会阻止事件继续冒泡,还会阻止同一个元素上的其他事件监听器被触发。

我们来修改一下上面的例子,给 father 元素添加两个点击事件监听器,并在第一个监听器中使用 stopImmediatePropagation

<div id="grandpa">
  <div id="father">
    <button id="son">Click Me!</button>
  </div>
</div>

<script>
  const father = document.getElementById('father');
  const son = document.getElementById('son');
  const grandpa = document.getElementById('grandpa');

  father.addEventListener('click', function(event) {
    console.log('Father clicked! - First Listener');
    event.stopImmediatePropagation(); // 阻止事件冒泡和同一元素上的其他监听器
  });

  father.addEventListener('click', function(event) {
    console.log('Father clicked! - Second Listener'); // 这行代码不会被执行
  });

  son.addEventListener('click', function(event) {
    console.log('Son clicked!');
  });

  grandpa.addEventListener('click', function(event){
      console.log('Grandpa clicked!');
  });
</script>

现在,如果你点击 <button id="son"> 按钮,控制台会输出:

Son clicked!
Father clicked! - First Listener

可以看到,Father clicked! - Second ListenerGrandpa clicked! 都没有被输出,因为 stopImmediatePropagation 阻止了事件冒泡和 father 元素上的第二个监听器被触发。

总结一下 stopPropagationstopImmediatePropagation 的区别:

方法 作用
stopPropagation() 阻止事件继续冒泡到父元素,但同一个元素上的其他事件监听器仍然会被触发。
stopImmediatePropagation() 阻止事件继续冒泡到父元素,并且阻止同一个元素上的其他事件监听器被触发。

三、preventDefault:江湖人称“反默认行为大师”

接下来,我们来认识一下另一位主角—— preventDefault。它的作用是阻止元素的默认行为,就像一个“反默认行为大师”,专门跟那些“固执己见”的元素作对。

什么是元素的默认行为呢?

不同的元素有不同的默认行为,比如:

  • 点击 <a> 标签会跳转到指定的链接。
  • 提交 <form> 表单会刷新页面。
  • 右键点击会弹出上下文菜单。
  • 选中一段文字可以进行复制。

preventDefault 可以阻止这些默认行为的发生,让你能够自定义元素的行为。

举个例子,假设你想要阻止点击 <a> 标签跳转到指定的链接,你可以这样做:

<a href="https://www.example.com" id="myLink">Click Me!</a>

<script>
  const myLink = document.getElementById('myLink');

  myLink.addEventListener('click', function(event) {
    event.preventDefault(); // 阻止默认行为
    console.log('Link clicked, but no navigation!');
  });
</script>

现在,如果你点击 <a id="myLink"> 标签,控制台会输出 Link clicked, but no navigation!,但是页面不会跳转到 https://www.example.com

为什么要阻止默认行为?

你可能会问,为什么要阻止默认行为呢?它有什么用呢?

答案是:为了实现更灵活、更自定义的交互效果。

举个例子,你可以阻止表单提交的默认行为,然后使用 AJAX 技术将表单数据发送到服务器,而不需要刷新页面,从而提高用户体验。

另一个常见的应用场景是:自定义右键菜单。你可以阻止默认的上下文菜单弹出,然后显示你自己的自定义菜单。

preventDefault 的注意事项

需要注意的是,并不是所有的事件都可以使用 preventDefault 来阻止默认行为。只有那些具有默认行为的事件才能被阻止。

你可以通过 event.cancelable 属性来判断一个事件是否可以被阻止。如果 event.cancelable 的值为 true,则表示该事件可以被阻止,否则不能被阻止。

myLink.addEventListener('click', function(event) {
  console.log('Is cancelable:', event.cancelable); // 输出 true
  event.preventDefault();
});

四、stopPropagationpreventDefault 的爱恨情仇

stopPropagationpreventDefault 就像一对欢喜冤家,它们经常一起出现,共同维护前端世界的秩序。

它们之间的区别在于:

  • stopPropagation 阻止的是事件的传播,也就是阻止事件从一个元素传递到另一个元素。
  • preventDefault 阻止的是元素的默认行为,也就是阻止元素按照预定的方式进行操作。

它们之间的联系在于:

  • 有些情况下,阻止事件的传播也可能会间接地阻止元素的默认行为。
  • 有些情况下,阻止元素的默认行为也可能会间接地阻止事件的传播。

举个例子,假设你有一个嵌套的 <div> 元素和一个 <a> 标签:

<div id="outer">
  <a href="https://www.example.com" id="inner">Click Me!</a>
</div>

如果你在 outer 元素上添加一个点击事件监听器,并在监听器中使用 stopPropagation

const outer = document.getElementById('outer');
const inner = document.getElementById('inner');

outer.addEventListener('click', function(event) {
  console.log('Outer clicked!');
  event.stopPropagation();
});

那么,当你点击 <a id="inner"> 标签时,Outer clicked! 会被输出,但是页面不会跳转到 https://www.example.com

这是因为 stopPropagation 阻止了事件冒泡到 outer 元素,因此 outer 元素的点击事件监听器被触发,而 <a> 标签的默认行为(跳转到链接)也被阻止了。

五、总结与最佳实践

今天,我们深入探讨了 stopPropagationpreventDefault 这两位前端界的“反骨仔”,了解了它们的脾气秉性和应用场景。

最后,我想给大家一些使用 stopPropagationpreventDefault 的最佳实践:

  • 谨慎使用: stopPropagationpreventDefault 会改变事件的传播和元素的默认行为,因此需要谨慎使用,避免产生意想不到的副作用。
  • 只在必要时使用: 只有在确实需要阻止事件传播或默认行为时才使用它们,不要滥用。
  • 考虑事件委托: 使用事件委托可以减少事件监听器的数量,提高代码的效率和可维护性。
  • 使用 event.cancelable 检查: 在使用 preventDefault 之前,先使用 event.cancelable 检查事件是否可以被阻止。

希望今天的讲解能够帮助大家更好地理解和使用 stopPropagationpreventDefault,让它们成为你手中的利器,而不是绊脚石。

最后,祝大家编码愉快,Bug 远离!谢谢大家! 😊

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注