[教學] 瀏覽器事件:Event Bubbling, Event Capturing 及 Event Delegation

February 25, 2018

分類標籤:javascript web browser frontend interview

這篇教學介紹瀏覽器事件的運作原理,包含event bubbling、event capturing。第二部分介紹event delegation的寫法及應用時機。教學內容主要參考javacript.infoIntroduction into Events

目錄

Event Bubbling

Event Bubbling指的是當某個事件發生在某個DOM element上(如:點擊),這個事件會觸發DOM elemtn的event handler,接下來會再觸發他的parent的event handler,以及parent的parent的event handler…直到最上層。

以下的例子中,點擊p會依序觸發p -> div -> form的onclick handler。

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P
    </p>
  </div>
</form>

Parent element的event handler中可以得到實際觸發事件的DOM element的資訊。實際觸發event的DOM element稱為target。上面的例子中,點擊p時target就是p,點擊div時target就是div,以此類推。

Event handler中可以透過event.target物件存取target。而event handler中,如果要存取event handler綁定的DOM element本身,可以透過this或是event.currentTarget存取。(如果需要bind this到別的東西的情況可用event.currentTarget。)

form.onclick = function(event) {
  console.log(event.target) // the element that is actually clicked, say p
  console.log(event.currentTarget) // form
}

Event capturing

和Event bubbling相反,event發生在某個DOM element上的時候,會從他的最上層parent開始觸發capturing handler,再來是倒數第二上層的ancestor的capturing handler,以此類推,直到觸發事件的DOM element本身的capturing handler。

如果要設定capturing handler,將addEventListener的第三個參數設為true(預設值為false):

form.addEventListener('click', handler, true)

因此,如果把event bubbling和event capturing的機制一起看的話,順序會是由上而下的Event capturing -> 由下而上的Event bubbling,所以依序會觸發的event handler是:

  1. Capturing (form)
  2. Capturing (div)
  3. Capturing (p)
  4. Bubbling (p)
  5. Bubbling (div)
  6. Bubbling (form)

Event Delegation

Event delegation指的是:假設同時有很多DOM element都有相同的event handler,與其在每個DOM element上個別附加event handler,不如利用event bubbling的特性,統一在他們的ancestor的event handler處理。

舉個例子,假設有個list,按下去的時候要顯示item所代表的資料:

<ol id="list">
  <li data-num="1"><em>1</em></li>
  <li data-num="2"><em>2</em></li>
  <li data-num="3"><em>3</em></li>
  <li data-num="4"><em>4</em></li>
</ol>

雖然可以對個別的li附加click event hander,但也可以透過event delegation的方式,統一在ol處理:

list.addEventListener('click', e => {
  // 檢查被按的元件確實在這個list裡面
  const li = e.target.closest('li')
  if (!li || !list.contains(li)) return

  alert(li.dataset.num)
})

注意e.target是實際上觸發click event的DOM element,可能是li或是li的descendants (如此處的em),所以handler前兩行是為了要檢查這個target是不是在這個ol底下。

即使動態增加list item,也不用寫額外的code幫他們新增click event handler,是不是挺方便的!

Behavior Pattern

我們可以利用event delegation的機制,幫element增加”行為”。做法是:

  1. 在需要某種行為的DOM element增加attribute。
  2. 在最上層的document增加event listener,並檢查元件是否帶有特定attribute。
Counter: <input type="button" value="1" data-counter>

<script>
  document.addEventListner('click', e => {
    if (typeof e.target.dataset.counter !== undefined) {
      e.target.value++
    }
  })
</script>

Reference

Introduction into Events - javascript.info


Profile picture

Shubo Chao 軟體工程師,目前大多專注於前端開發