這篇教學介紹瀏覽器事件的運作原理,包含event bubbling、event capturing。第二部分介紹event delegation的寫法及應用時機。教學內容主要參考javacript.info 的 Introduction into Events。
目錄
Event Bubbling
Event Bubbling指的是當某個事件發生在某個DOM element上(如:點擊),這個事件會觸發DOM element的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是:
- Capturing (form)
- Capturing (div)
- Capturing (p)
- Bubbling (p)
- Bubbling (div)
- Bubbling (form)
Event Delegation
Event delegation指的是:假設同時有很多DOM element都有相同的event handler,與其在每個DOM element上個別附加event handler,不如利用event bubbling的特性,統一在他們的ancestor的event handler處理。
舉個例子,假設有個 list,按下去的時候要刪除 item:
<ul id="todoList">
<!-- The ID is stored in the data-id attribute -->
<li class="todo-item">
<button class="delete-btn" data-id="123">
<span>Delete</span>
</button>
</li>
<li class="todo-item">
<button class="delete-btn" data-id="456">
<span>Delete</span>
</button>
</li>
</ul>
雖然可以對個別的 button 附加click event handler,但也可以透過event delegation的方式,統一在 ul 處理:
list?.addEventListener('click', e => {
if (!(e.target instanceof Element)) return;
const btn = e.target.closest<HTMLButtonElement>(
"button.delete-btn"
);
if (!btn || !list.contains(btn)) return;
const productId = btn.dataset.id;
if (!productId) return;
deleteProduct(productId);
})即使動態增加list item,也不用寫額外的code幫他們新增click event handler,是不是挺方便的!
Behavior Pattern
我們可以利用event delegation的機制,幫element增加”行為”。做法是:
- 在需要某種行為的DOM element增加attribute。
- 在最上層的
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>