DOM - 事件傳遞機制
- DOM 的事件在傳播時,會先從根節點開始往下傳遞到 target,再往上從子節點一路逆向傳回去根節點。這段過程中共分為三個階段:
 捕獲 => 目標本身 => 冒泡
 整個傳遞機制的流程就是:
 1.捕獲:
- 從根節點開始往下傳遞到 target 的過程
- 以點選 <td>標籤為例,事件得傳遞可能會像這樣:- window => document => <html>=><body>=><table>=><tbody>=><tr>
 
- window => document => 
目標本身:
- 事件傳遞到目標本身
- 不會分捕獲或冒泡
- 以點選 <td>標籤為例,事件傳遞就在目前這個目標<td>本身
2.冒泡:
- 從子節點一路逆向傳回去根節點的過程
- 以點選 - <td>標籤為例,事件得傳遞可能會像這樣:- <tr>=>- <tbody>=>- <table>=>- <body>=>- <html>=> document => window
 
- 注意:掌握原則 
 1.先捕獲,再冒泡
 2.當事件傳到 target 本身,若要裝監聽器的話,監聽器沒有分捕獲(左邊)跟冒泡(右邊),監聽器會裝在target 本身
 3.捕獲與冒泡是「無論如何」都會發生的,而且順序永遠不會改變的一個東西。當你點擊某個按鈕時,就會先從 window 一路把事件由左邊傳遞下去,再從按鈕一路把事件傳遞由右邊傳遞回來。所以,儘管你什麼 event listener 都沒有加,背後的捕獲與冒泡還是存在。
 事件機制也是這樣的,捕獲跟冒泡這個流程永遠都在,但如果你沒有加監聽器,你是察覺不到的。所以 addEventListener 的第三個參數只是覺得你要在「哪邊」加上這個監聽器,而不是改變原本事件傳遞的流程。
設定於哪個階段做監聽
- 在 .addEventListener() 加上第三個參數- true:捕獲
- false:冒泡
- 若不寫第三個參數,則預設為 false
 
取消事件傳遞
e.stopPropagation()
- 加在哪個階段上,事件的傳遞就斷在哪裡,不會再把事件傳遞給「下一個節點」
- .outer => .inner => .btn - 加在.outer「捕獲階段」上 
 .outer「觸發」捕獲事件,不再傳遞給下一個節點
 .outer捕獲 1
- 加在.btn「冒泡階段」上 
 .btn「觸發」冒泡事件,不再傳遞給下一個節點
 .outer捕獲1 => .inner捕獲1 => .btn冒泡2 => .btn捕獲2
 
停止後續 v.s 停止預設
e.stopPropagation 停止的是後續事件
e.stopImmediatePropagation() 讓其他同一層級的 listener 也不要被執行
e.preventDefault() 可以停止瀏覽器的預設行為
如果我想要阻止頁面上所有的 click 事件( 包括 <a> 預設的動作 ),可以在 window 物件監聽捕獲階段,來阻止底下的所有元素
window.addEventListener('click', function (e) {
  e.preventDefault(); // 停止預設功能
  e.stopPropagation(); // 停止後續傳遞
}, true) // 指定為從捕獲階段(左邊)開始監聽
// 底下的事件傳遞全都被阻止了
function addEvent(className) {
  document.querySelector(className)
    .addEventListener('click', function (e) {
      console.log(className, '捕獲', e.eventPhase);
    }, true)
  document.querySelector(className)
  .addEventListener('click', function (e) {
    console.log(className, '冒泡', e.eventPhase);
  }, false)
};
新手易混肴問題
利用迴圈幫全部按鈕加上事件監聽,希望利用迴圈的 i 值,在按鈕被點選的時候加上編號 i。
const btnGroup = document.querySelectorAll('.btn');
for (var i = 1; i <= btnGroup.length; i += 1) {
  btnGroup[i].addEventListener('click', function (e) {
    console.log(i);
  })
}
但是實際上當 click 事件觸發時,迴圈已經跑完了(i 已經跑到 btnGroup.length),所以編號會通通是最後一號。
- 解決方式:
 利用屬性 attribute 來紀錄每一圈新增按鈕時候的 i 值。這樣迴圈跑完後雖然 i 值已經跑到 btnGroup.length了, 每個按鈕依然有自己的 attribute 可以抓
<button class="btn" data-value="1">1</button>
<button class="btn" data-value="2">2</button>
<script>
  const btnGroup = document.querySelectorAll('.btn');
  for (let i = 0; i < btnGroup.length; i += 1) {
    btnGroup[i].addEventListener('click', function (e) {
      console.log(e.target.getAttribute('data-value'));
    })
  }
</script>
事件代理 Event delegation
利用事件傳遞的機制,把 button 的 eventListener 綁定在上層的 .outer 元素上,就叫做事件代理
<div class="outer">
  <button class="btn_add" >add</button>
  <button class="btn" data-value="1">1</button>
  <button class="btn" data-value="2">2</button>
</div>
<script>
  document.querySelector('.outer').addEventListener('click',  function (e) {
  console.log(e.target.getAttribute('data-value'));
})
</script>
什麼是 event delegation,為什麼我們需要它?
如果我們要把非常大量的「按鈕」加上監聽器,我們可以手動一個一個加,或者利用迴圈幫忙加上去。
但是我們有更方便的作法,就是把監聽器加在這些按鈕共同的「父層元素」。因為事件的傳遞從根節點到 target 的過程一定會經過這個「父層元素」。(上面有提到捕獲與冒泡是「無論如何」都會發生的,且事件的傳遞順序永遠不會改變。)這種由父層元素協助監聽的方式,就叫做「事件代理」。
- 比起手動一個一個加上監聽器,事件代理的效率更高
- 在這個父層元素底下動態新增的子元素,也可以一併綁定到 eventListener


![[ week 1 ]  Git  vs. GitHub](https://static.coderbridge.com/images/covers/default-post-cover-3.jpg)