DOM 事件以及preventDefault(),stopPropagation()和return false;

DOM 事件

冒泡:window 是终点,向上传播;

捕获:window 是起点,向下传播;

addEventListener 的第三个参数默认是 false 冒泡模式,true 则为捕获模式。不同模式,会影响到事件的传播路径,具体看后面的示例代码。

在元素上同时绑定捕获事件和冒泡事件,如果通过此元素的子级元素触发,则优先触发捕获事件,若不通过此元素的子级元素触发,则按照 Javascript 执行顺序触发。

事件干预

preventDefault()

阻止默认行为,比如 a 的跳转,上传 input 的弹框;

stopPropagation()

阻止冒泡,事件不再向上传播;

return false;

不能阻止默认行为,也不能阻止冒泡。

这个最容易出错,网上也很多错误的文章。

为什么出错?因为 jQuery 环境下,return false;确实阻止了默认行为,还阻止了冒泡。

代码示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DOM事件</title>
    <style>
      #grandfather {
        width: 300px;
        height: 300px;
        background: #666;
      }

      #father {
        width: 200px;
        height: 200px;
        background: #999;
      }

      #child {
        width: 100px;
        height: 100px;
        background: #aaa;
      }
    </style>
  </head>
  <body>
    <p>
      addEventListener第三个参数默认为false冒泡模式,true则为捕获模式。
      <br />
      冒泡模式:window终点,向上传播;
      <br />
      捕获模式:window起点,向下传播
      <br />
      DOM事件的传播顺序是先捕获后冒泡;如果同时指定了捕获和冒泡事件,则取决于代码的先后顺序。
      <br />
      return false 并不能阻止冒泡,也不能阻止默认行为,只有开启干预模式才行。
    </p>

    <div id="grandfather">
      grandfather
      <div id="father">
        father
        <div id="child">
          <a href="#child">child</a>
          <input type="file" />
        </div>
      </div>
    </div>

    <div>
      开启干预:<span id="state">false</span>
      <button id="btn">切换</button>
    </div>

    <script>
      let grandfather = document.getElementById('grandfather');
      let father = document.getElementById('father');
      let child = document.getElementById('child');

      var btn = document.getElementById('btn');
      var state = document.getElementById('state');
      btn.addEventListener('click', function () {
        state.innerText = getState() ? 'false' : 'true';
        console.log('干预切换成功', state.innerText);
      });

      function getState() {
        return state.innerText === 'true';
      }

      // 冒泡模式
      child.addEventListener('click', function () {
        if (getState()) {
          event.preventDefault(); // 阻止了默认行为
          console.log('child使用preventDefault阻止了默认行为,因此URL不会出现hash,文件弹框也不会弹出');
        }
        console.log('冒泡模式: child');
        return false; // 并不能阻止冒泡,也不能阻止默认行为
      });
      father.addEventListener('click', function () {
        if (getState()) {
          event.stopPropagation(); // 阻止了冒泡
          console.log('father使用stopPropagation阻止了冒泡,因此grandfather不会被触发');
        }
        console.log('冒泡模式: father');
        return false; // 并不能阻止冒泡,也不能阻止默认行为
      });
      grandfather.addEventListener('click', function () {
        console.log('冒泡模式: grandfather');
        return false; // 并不能阻止冒泡,也不能阻止默认行为
      });

      // 捕获模式
      grandfather.addEventListener(
        'click',
        function () {
          console.log('捕获模式: grandfather');
        },
        true,
      );
      father.addEventListener(
        'click',
        function () {
          console.log('捕获模式: father');
        },
        true,
      );
      child.addEventListener(
        'click',
        function () {
          console.log('捕获模式: child');
        },
        true,
      );
    </script>
  </body>
</html>

参考

https://stackoverflow.com/questions/30473581/when-to-use-preventdefault-vs-return-false

© 2022  Arvin Xiang
Built with ❤️ by myself