事件传播


在事件发生时,会有个Event实例收集事件的相关信息,在遵守标准的浏览器上,Event实例会作为事件处理器的的第一个参数,若要获取操作的目标对象,可以透过Event实例的target特性。

那么操作的目标对象是指什么呢?如果在按钮上点选,那么按钮就是操作的目标对象,在〈基本事件模型〉中有说明过,触发事件时,事件处理器的this会设定为当时的元素,那么为何还要有特性指出操作目标对象?

事实上,在〈基本事件模型〉中,操作时若发生事件,并事件不仅停于操作的元素,还会从操作的元素往外传播,若外层元素亦有设定对应的事件处理器,亦会调用事件处理器,这可以用下面的范例来示范:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">

    <div id="divId">
        <button id="btnId">按我</button>            
    </div>
    <span id="console"></span>

<script type="text/javascript">
      function handler(event) {
          let target = event.target;
          document.getElementById('console').innerHTML += 
             `<br><b>this.id:</b> ${this.id}, <b>target.id:</b> ${target.id}`;
      }

      document.getElementById('bodyId').onclick = handler;
      document.getElementById('divId').onclick = handler;
      document.getElementById('btnId').onclick = handler;
</script>  
</body>
</html>

按我观看结果

在上例中,按钮是包括在<div>中,而<body><div>的外层元素,三者都设定了事件处理器。如果试着按下按钮,则会看到结果如下:

this.id: btnId, target.id: btnId
this.id: divId, target.id: btnId
this.id: bodyId, target.id: btnId

不仅按钮的事件处理器被调用,外层<div><body>也依序被调用,这样的行为叫作事件气泡传播(Event Bubbling),事件传播至元素并调用事件处理器时,this就设定为该元素,这可以从this.id的显示结果观察到,并注意到,由于操作时按下的是按钮,所以操作目标元素就是按钮,这可以由target.id观察到。

事件气泡传播可以善用。例如在〈修改文件〉中动态新增图片的例子,每创建一个新的<img>,就设定该<img>click事件处理器,以便在点选图片时自动移除图片。若利用事件气泡传播,可以只在<div>上设定一次事件处理器,完成相同的结果。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <input id="src" type="text"><button id="add">新增图片</button>
    <div id="images"></div>

<script type="text/javascript">
    let images = document.getElementById('images');
    images.onclick = function(event) {
        this.removeChild(event.target);
    };
    document.getElementById('add').onclick = function() {
        let img = document.createElement('img');
        img.src = document.getElementById('src').value;
        images.appendChild(img);
    };
</script>  

  </body>
</html>

按我观看结果

如果想要停止事件传播,在遵守标准的浏览器上,必须调用EventstopPropagation()方法。例如要将第一个范例停止目标元素外的事件传播,可以如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">

    <div id="divId">
        <button id="btnId">按我</button>            
    </div>
    <span id="console"></span>

<script type="text/javascript">
      function handler(event) {
          let target = event.target;
          document.getElementById('console').innerHTML += 
             `<br><b>this.id:</b> ${this.id}, <b>target.id:</b> ${target.id}`;
          event.stopPropagation();
      }

      document.getElementById('bodyId').onclick = handler;
      document.getElementById('divId').onclick = handler;
      document.getElementById('btnId').onclick = handler;
</script>  
</body>
</html>

按我观看结果

在〈DOM Level 2 事件模型〉中,事件会历经两个传播阶段,当事件发生时,会先从document往内传播至操作目标元素,这个阶段称之为捕捉阶段(Capturing phase),接着事件再从操作目标元素往外传播至document,这个阶段称之为气泡阶段(Bubbling phase)。

addEventListener方法的第三个参数若为true,表示事件处理器将作为捕捉阶段处理器,若为false则为气泡阶段处理器。

例如,可改写第一个范例,同时设定两个阶段的处理器来观察事件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
    <div id="divId">
        <button id="btnId">按我</button>
    </div>
    <span id="console"></span>

<script type="text/javascript">

      function handler(event) {
          let currentTarget = event.currentTarget;
          let target = event.target;
          document.getElementById('console').innerHTML += 
              `<br><b>currentTarget.id:</b> ${currentTarget.id}, <b>target.id:</b> ${target.id}`;
      }

      document.getElementById('bodyId').addEventListener('click', handler, true);
      document.getElementById('bodyId').addEventListener('click', handler, false);

      document.getElementById('divId').addEventListener('click', handler, true);
      document.getElementById('divId').addEventListener('click', handler, false);

      document.getElementById('btnId').addEventListener('click', handler, true);
      document.getElementById('btnId').addEventListener('click', handler, false);

</script>  
</body>
</html>

按我观看结果

操作的目标元素,可以使用Eventtarget特性获取。如果按下按钮,会发现以下的结果,可发现事件先从外往内,再从内往外传播:

currentTarget.id: bodyId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: bodyId, target.id: btnId

如果想要停止事件传播,也是调用EventstopPropagation方法。例如上一个范例若仅注册冒泡处理器,要停止目标元素外的事件传播,可以如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
    <div id="divId">
        <button id="btnId">按我</button>
    </div>
    <span id="console"></span>

<script type="text/javascript">

      function handler(event) {
          let currentTarget = event.currentTarget;
          let target = event.target;
          document.getElementById('console').innerHTML += 
              `<br><b>currentTarget.id:</b> ${currentTarget.id}, <b>target.id:</b> ${target.id}`;
          event.stopPropagation();
      }

      document.getElementById('bodyId').addEventListener('click', handler, false);
      document.getElementById('divId').addEventListener('click', handler, false);
      document.getElementById('btnId').addEventListener('click', handler, false);

</script>  
</body>
</html>

按我观看结果


展开阅读全文