JS事件冒泡机制和兼容性添加事件

本篇文章主要来讲讲 事件的冒泡机制 和 添加事件的几种方法。


一. JS的事件传递顺序: 捕获阶段 -> 目标阶段 -> 冒泡阶段

  • 捕获阶段是指从父层往子层找。比如 <body><div></div></body> ,我们会先找到 body,然后下一个才是 div。就像*领导发布政策一样。 从上往下通知。

  • 目标阶段就是找到目标的那段时间,这个我们暂且不谈。

  • 冒泡阶段就是从子层往外层传。比如 <body><div></div></body> ,这个时候是先写 div ,再找 body。往外依次冒泡。从内往外通知。

    先从外到内传一圈波纹,再从内到外传一圈波纹

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
      <div >
        <div >
          点我
        </div>
      </div>
    </body>
    <script type="text/javascript">
      const oBtn = document.getElementById('div1')
      const div1 = document.getElementById('div1')
      const div2 = document.getElementById('div2')
      // addEventListener 的第三个参数默认为 false —— 表示该事件在冒泡阶段触发; true 则表示在捕获阶段触发
      div1.addEventListener('click', function (event) {
        console.log("A");
      }, true);
      div2.addEventListener('click', function (event) {
        console.log("B");
      });
      div1.addEventListener('click', function (event) {
        console.log("C")
      }, false);
      div2.addEventListener('click', function (event) {
        console.log("D");
      }, true);
    </script>
    </html>
    

答案是: DACB

根据 JS 的 dom 事件顺序 ———— “从外到内,从内到外” 和 addEventListener第三个参数所控制的触发阶段我们可以很容易地解答。

  • 1.div2 在外面,div1 在里面。捕获阶段时优先;掠过 div2 后再掠过 div1,发现他们要找的人是 D 和 A (因为第三个参数为true)
  • 2.然后到了 冒泡阶段;掠过 div1 后途经 div2;他们要找的分别是捆绑在 div1 上的 C 然后是 B。

二. 兼容性地添加 JS 事件

如果我们要给按钮添加事件,我们会怎么做呢?

也许我们会这么做<button onclick="console.log('1')">点我</button>
很明显地,它破坏了标签。我们应该让 html 和 事件分离,否则,我们下一次要修改这个按钮就不好操作了。
他有个致命的缺点:那就是只能用此方法添加一个事件,后面的会覆盖前面(原理是指向了不同的对象)。

  • 错误示范:
      const oBtn = document.getElementsByTagName('button')[0];
      oBtn.onclick = function() {
        console.log('1');
      }
      // 只会显示下面这次
      oBtn.onclick = function() {
        console.log('我是第二次');
      }
    
  • 正确示范:
      const oBtn = document.getElementsByTagName('button')[0];
      // addEventListener
      oBtn.addEventListener("click", () => {console.log('1')});
      oBtn.addEventListener("click", () => {console.log('我是第二次')} );
      // 上面的两次都会被认可
    

我们已经决定放弃(不推荐)使用 onclick;但接下来还有一件事情要做,那就是兼容性问题。

事件并不总是如我们所想的运转,感觉失望和沮丧是徒劳无益的,我们要想办法改变我们自己适应这个世界。

IE 6/7/8 上并没有 addEventListener.它们使用了特殊的 attachEvent;对于这样的少数人,我们也不能完全放弃。

代码的健壮性是锦上添花还是...至关重要的?也许这是裁定程序员优秀的标准吧。
遂改成这样。(可以在老IE和Chrome上感受一下差距)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div >
    <div >
      点我
    </div>
  </div>
</body>
<script type="text/javascript">
  var oBtn = document.getElementById('div1')
  var div1 = document.getElementById('div1')
  var div2 = document.getElementById('div2')
  // 注意:attachEvent 只有冒泡没有捕获!!!
  var addEvent = function (dom, eventType, method, isBubble) {
    eventType = eventType || 'click'
    isBubble = isBubble || false
    if (dom.addEventListener) {
      dom.addEventListener(eventType, method, isBubble)
    } else if (dom.attachEvent) {
      dom.attachEvent('on' + eventType, method)
    }
  }
  var methodX = function () {
    console.log('methodX')
  }
  var method2 = function () {
    console.log('method2')
  }
  addEvent(div1, 'click', methodX)
  addEvent(div2, 'click', method2)
</script>
</html>

除此之外我们还想要知道的 addEventListener 和 attachEvent 都是讲究规则的。谁先来,谁在上面。

  // 多个 addEventListener 的执行顺序:谁先被添加,谁就在前面
  div1.addEventListener('click', event => {
    console.log('first')
  })
  div1.addEventListener('click', event => {
    console.log('second')
  })
  div1.addEventListener('click', event => {
    console.log('third')
  })

complete